import * as path from "path";
import * as vscode from "vscode";

import { vscodeVariables } from "./vscode-variables";

export interface TinymistConfig {
  [key: string]: any;
  typingContinueCommentsOnNewline?: boolean;
  serverPath?: string;
}

export const HTML_PREVIEWER_PREFIX = "html:";

const INJECTED_CLIENT_OPTION_CONFIG_KEYS = [
  "triggerSuggest",
  "triggerParameterHints",
  "triggerSuggestAndParameterHints",
  "supportHtmlInMarkdown",
  "supportClientCodelens",
  "supportExtendedCodeAction",
  "customizedShowDocument",
  "delegateFsRequests",
];

export function loadTinymistConfig(): TinymistConfig {
  let config: Record<string, any> = JSON.parse(
    JSON.stringify(vscode.workspace.getConfiguration("tinymist")),
  );
  config.colorTheme = "light";

  const keys = Object.keys(config);
  let values = keys.map((key) => config[key]);
  values = substVscodeVarsInConfig(keys, values);
  config = {};
  for (let i = 0; i < keys.length; i++) {
    config[keys[i]] = values[i];
  }
  if (typeof config.previewer === "string") {
    config.previewer = resolvePreviewerValue(config.previewer);
  }
  return config;
}

export function patchInjectedClientOptionsInConfig(
  keys: (string | undefined)[],
  values: unknown[],
  config: TinymistConfig,
): unknown[] {
  return values.map((value, i) => {
    const section = keys[i];
    if (section === "tinymist" && isObjectRecord(value)) {
      return patchInjectedClientOptionsObject(value, config);
    }

    const key = tinymistConfigKey(section);
    if (
      key &&
      INJECTED_CLIENT_OPTION_CONFIG_KEYS.includes(key) &&
      (value === undefined || value === null) &&
      Object.prototype.hasOwnProperty.call(config, key)
    ) {
      return config[key];
    }

    return value;
  });
}

const STR_VARIABLES = [
  "serverPath",
  "tinymist.serverPath",
  "rootPath",
  "tinymist.rootPath",
  "outputPath",
  "tinymist.outputPath",
];
const STR_ARR_VARIABLES = ["fontPaths", "tinymist.fontPaths"];
const COLOR_THEME = ["colorTheme", "tinymist.colorTheme"];

// todo: documentation that, typstExtraArgs won't get variable extended
export function substVscodeVarsInConfig(
  keys: (string | undefined)[],
  values: unknown[],
): unknown[] {
  return values.map((value, i) => {
    const k = keys[i];
    if (!k) {
      return value;
    }
    if (COLOR_THEME.includes(k)) {
      return determineVscodeTheme();
    }
    if (STR_VARIABLES.includes(k)) {
      return substVscodeVars(value as string);
    }
    if (STR_ARR_VARIABLES.includes(k)) {
      return substFontPaths(value);
    }
    return value;
  });
}

function substVscodeVars(str: string | null | undefined): string | undefined {
  if (str === undefined || str === null) {
    return undefined;
  }
  try {
    return vscodeVariables(str);
  } catch (e) {
    console.error("failed to substitute vscode variables", e);
    return str;
  }
}

export function resolvePreviewerValue(value: string | null | undefined): string | undefined {
  if (value === undefined || value === null) {
    return undefined;
  }

  const trimmedValue = value.trim();
  if (!trimmedValue) {
    return undefined;
  }

  if (!trimmedValue.startsWith(HTML_PREVIEWER_PREFIX)) {
    return trimmedValue;
  }

  const providerPath = trimmedValue.slice(HTML_PREVIEWER_PREFIX.length).trim();
  const resolvedProviderPath = substVscodeVars(providerPath) ?? providerPath;
  return `${HTML_PREVIEWER_PREFIX}${
    resolvedProviderPath ? path.normalize(resolvedProviderPath) : resolvedProviderPath
  }`;
}

function substFontPaths(value: unknown): string[] | undefined {
  if (value === undefined || value === null) {
    return undefined;
  }
  if (!isStringArray(value)) {
    const invalidFontPathsShape = Array.isArray(value)
      ? "an array with non-string entries"
      : typeof value;
    void vscode.window.showErrorMessage(
      `Tinymist ignored "tinymist.fontPaths" setting because it must be an array of strings, for example \`["fonts"]\`. Received ${invalidFontPathsShape}.`,
    );
    return undefined;
  }
  return value.map((path) => substVscodeVars(path) ?? path);
}

function isStringArray(value: unknown): value is string[] {
  return Array.isArray(value) && value.every((entry) => typeof entry === "string");
}

function tinymistConfigKey(section: string | undefined): string | undefined {
  if (!section) {
    return undefined;
  }
  return section.startsWith("tinymist.") ? section.slice("tinymist.".length) : section;
}

function patchInjectedClientOptionsObject(
  value: Record<string, unknown>,
  config: TinymistConfig,
): Record<string, unknown> {
  const next = { ...value };
  for (const key of INJECTED_CLIENT_OPTION_CONFIG_KEYS) {
    if (
      (next[key] === undefined || next[key] === null) &&
      Object.prototype.hasOwnProperty.call(config, key)
    ) {
      next[key] = config[key];
    }
  }
  return next;
}

function isObjectRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function determineVscodeTheme(): any {
  // console.log("determineVscodeTheme", vscode.window.activeColorTheme.kind);
  switch (vscode.window.activeColorTheme.kind) {
    case vscode.ColorThemeKind.Dark:
    case vscode.ColorThemeKind.HighContrast:
      return "dark";
    default:
      return "light";
  }
}

// "tinymist.hoverPeriscope": {
//     "title": "Show preview document in periscope mode on hovering",
//     "description": "In VSCode, enable compile status meaning that the extension will show the compilation status in the status bar. Since Neovim and helix don't have a such feature, it is disabled by default at the language server label.",
//     "type": [
//         "object",
//         "string"
//     ],
//     "default": "disable",
//     "enum": [
//         "enable",
//         "disable"
//     ],
//     "properties": {
//         "yAbove": {
//             "title": "Y above",
//             "description": "The distance from the top of the screen to the top of the periscope hover.",
//             "type": "number",
//             "default": 55
//         },
//         "yBelow": {
//             "title": "Y below",
//             "description": "The distance from the bottom of the screen to the bottom of the periscope hover.",
//             "type": "number",
//             "default": 55
//         },
//         "scale": {
//             "title": "Scale",
//             "description": "The scale of the periscope hover.",
//             "type": "number",
//             "default": 1.5
//         },
//         "invertColors": {
//             "title": "Invert colors",
//             "description": "Invert the colors of the periscope to hover.",
//             "type": "string",
//             "enum": [
//                 "auto",
//                 "always",
//                 "never"
//             ],
//             "default": "auto"
//         }
//     }
// },
