import {
  ApplyWorkspaceEditParams,
  ApplyWorkspaceEditResult,
  CodeAction,
  CodeActionParams,
  CompletionList,
  CompletionParams,
  DefinitionParams,
  DidChangeWatchedFilesParams,
  DocumentFormattingParams,
  DocumentHighlight,
  DocumentHighlightParams,
  DocumentSymbol,
  DocumentSymbolParams,
  ExecuteCommandParams,
  FoldingRange,
  FoldingRangeParams,
  Hover,
  HoverParams,
  InitializeParams,
  InitializeResult,
  InitializedParams,
  Location,
  PrepareRenameParams,
  PublishDiagnosticsParams,
  Range,
  ReferenceParams,
  RenameFilesParams,
  RenameParams,
  SemanticTokens,
  SemanticTokensParams,
  SignatureHelp,
  SignatureHelpParams,
  TextDocumentChangeEvent,
  TextDocumentIdentifier,
  WorkspaceEdit,
  WorkspaceFolder,
  WorkspaceFoldersChangeEvent,
} from "vscode-languageserver";
import type { TextDocument, TextEdit } from "vscode-languageserver-textdocument";
import type { CompilerOptions } from "../core/options.js";
import type { Program } from "../core/program.js";
import type {
  CompilerHost,
  Diagnostic,
  NoTarget,
  SourceFile,
  SourceLocation,
  TypeSpecScriptNode,
} from "../core/types.js";
import { LoadedCoreTemplates } from "../init/core-templates.js";
import { EmitterTemplate, InitTemplate, InitTemplateLibrarySpec } from "../init/init-template.js";
import { ScaffoldingConfig } from "../init/scaffold.js";
import { CompileTracker, ServerCompileOptions } from "./server-compile-manager.js";

export type ServerLogLevel = "trace" | "debug" | "info" | "warning" | "error";
export interface ServerLog {
  level: ServerLogLevel;
  message: string;
  detail?: unknown;
}

export interface ServerHost {
  readonly compilerHost: CompilerHost;
  readonly throwInternalErrors?: boolean;
  readonly getOpenDocumentByURL: (url: string) => TextDocument | undefined;
  readonly sendDiagnostics: (params: PublishDiagnosticsParams) => void;
  readonly log: (log: ServerLog) => void;
  readonly applyEdit: (
    paramOrEdit: ApplyWorkspaceEditParams | WorkspaceEdit,
  ) => Promise<ApplyWorkspaceEditResult>;
  /**
   * This debounce delay function is designed to reduce the running time of the test and should not be overridden during normal use.
   * @returns debounce delay in milliseconds
   * @internal
   */
  readonly getDocumentUpdateDebounceDelay?: () => number;
}

export interface CompileResult {
  readonly program: Program;
  readonly document: TextDocument | undefined;
  readonly script: TypeSpecScriptNode | undefined;
  readonly optionsFromConfig: CompilerOptions;
  readonly tracker: CompileTracker;
}

export interface ServerDiagnostic extends Diagnostic {
  target: (SourceLocation & { position?: { line: number; column: number } }) | typeof NoTarget;
}

export interface InternalCompileResult {
  readonly hasError: boolean;
  readonly diagnostics: ServerDiagnostic[];
  readonly entrypoint?: string;
  readonly options?: CompilerOptions;
}

export interface Server {
  readonly pendingMessages: readonly ServerLog[];
  readonly workspaceFolders: readonly ServerWorkspaceFolder[];
  compile(
    document: TextDocument | TextDocumentIdentifier,
    additionalOptions: CompilerOptions | undefined,
    serverCompileOptions: ServerCompileOptions,
  ): Promise<CompileResult | undefined>;
  initialize(params: InitializeParams): Promise<InitializeResult>;
  initialized(params: InitializedParams): void;
  workspaceFoldersChanged(e: WorkspaceFoldersChangeEvent): Promise<void>;
  watchedFilesChanged(params: DidChangeWatchedFilesParams): void;
  formatDocument(params: DocumentFormattingParams): Promise<TextEdit[]>;
  gotoDefinition(params: DefinitionParams): Promise<Location[]>;
  complete(params: CompletionParams): Promise<CompletionList>;
  findReferences(params: ReferenceParams): Promise<Location[]>;
  findDocumentHighlight(params: DocumentHighlightParams): Promise<DocumentHighlight[]>;
  prepareRename(params: PrepareRenameParams): Promise<Range | undefined>;
  rename(params: RenameParams): Promise<WorkspaceEdit>;
  renameFiles(params: RenameFilesParams): Promise<void>;
  getSemanticTokens(params: SemanticTokensParams): Promise<SemanticToken[]>;
  buildSemanticTokens(params: SemanticTokensParams): Promise<SemanticTokens>;
  checkChange(change: TextDocumentChangeEvent<TextDocument>): void;
  getHover(params: HoverParams): Promise<Hover>;
  getSignatureHelp(params: SignatureHelpParams): Promise<SignatureHelp | undefined>;
  getFoldingRanges(getFoldingRanges: FoldingRangeParams): Promise<FoldingRange[]>;
  getDocumentSymbols(params: DocumentSymbolParams): Promise<DocumentSymbol[]>;
  documentClosed(change: TextDocumentChangeEvent<TextDocument>): void;
  documentOpened(change: TextDocumentChangeEvent<TextDocument>): void;
  getCodeActions(params: CodeActionParams): Promise<CodeAction[]>;
  executeCommand(params: ExecuteCommandParams): Promise<void>;
  reportDiagnostics({ program, document, optionsFromConfig }: CompileResult): void;
  log(log: ServerLog): void;

  // Following custom capacities are added for supporting tsp init project from IDE (vscode for now) so that IDE can trigger compiler
  // to do the real job while collecting the necessary information accordingly from the user.
  // We can't do the tsp init experience by simple cli interface because the experience needs to talk
  // with the compiler for multiple times in different steps (i.e. get core templates, validate the selected template, scaffold the project)
  // and it's not a good idea to expose these capacity in cli interface and call cli again and again.
  getInitProjectContext(): Promise<InitProjectContext>;
  validateInitProjectTemplate(param: { template: InitTemplate }): Promise<boolean>;
  initProject(param: { config: InitProjectConfig }): Promise<boolean>;
  internalCompile(param: {
    doc: TextDocumentIdentifier;
    options: CompilerOptions;
  }): Promise<InternalCompileResult>;
}

export interface ServerSourceFile extends SourceFile {
  // Keep track of the open document (if any) associated with a source file.
  readonly document?: TextDocument;
}

export interface ServerWorkspaceFolder extends WorkspaceFolder {
  // Remember path to URL conversion for workspace folders. This path must
  // be resolved and normalized as other paths and have a trailing separator
  // character so that we can test if a path is within a workspace using
  // startsWith.
  path: string;
}

export enum SemanticTokenKind {
  Namespace,
  Type,
  Class,
  Enum,
  Interface,
  Struct,
  TypeParameter,
  Parameter,
  Variable,
  Property,
  EnumMember,
  Event,
  Function,
  Method,
  Macro,
  Keyword,
  Comment,
  String,
  Number,
  Regexp,
  Operator,

  DocCommentTag,
}

export interface SemanticToken {
  kind: SemanticTokenKind;
  pos: number;
  end: number;
}

export type CustomRequestName =
  | "typespec/getInitProjectContext"
  | "typespec/initProject"
  | "typespec/validateInitProjectTemplate"
  | "typespec/internalCompile";
export interface ServerCustomCapacities {
  getInitProjectContext?: boolean;
  validateInitProjectTemplate?: boolean;
  initProject?: boolean;
  internalCompile?: boolean;
}

export interface ServerInitializeResult extends InitializeResult {
  customCapacities?: ServerCustomCapacities;
  compilerRootFolder?: string;
  compilerCliJsPath?: string;
}

export interface InitProjectContext {
  /** provide the default templates current compiler/cli supports */
  coreInitTemplates: LoadedCoreTemplates;
}

export type InitProjectConfig = ScaffoldingConfig;
export type InitProjectTemplate = InitTemplate;
export type InitProjectTemplateLibrarySpec = InitTemplateLibrarySpec;
export type InitProjectTemplateEmitterTemplate = EmitterTemplate;
