import type { Extension } from '@codemirror/state';

// Static imports for the most common languages only.
// Less common languages are loaded dynamically via loadLanguageByExtension
// to keep the initial bundle lean.
import { javascript } from '@codemirror/lang-javascript';
import { json } from '@codemirror/lang-json';
import { css } from '@codemirror/lang-css';
import { html } from '@codemirror/lang-html';
import { markdown } from '@codemirror/lang-markdown';
import { languages } from '@codemirror/language-data';
import { python } from '@codemirror/lang-python';

import { Language, LanguageDescription, StreamLanguage, HighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { tags as t } from '@lezer/highlight';
import { shell } from '@codemirror/legacy-modes/mode/shell';

const shellLanguage = StreamLanguage.define(shell);

export function codeBlockLanguageResolver(info: string): Language | LanguageDescription | null {
  const normalized = info.trim().toLowerCase();

  switch (normalized) {
    case 'bash':
    case 'sh':
    case 'zsh':
    case 'shell':
    case 'shellsession':
    case 'console':
      return shellLanguage;
    case 'json':
    case 'jsonc':
    case 'json5':
      return json().language;
    case 'js':
    case 'javascript':
      return javascript().language;
    case 'jsx':
      return javascript({ jsx: true }).language;
    case 'ts':
    case 'typescript':
      return javascript({ typescript: true }).language;
    case 'tsx':
      return javascript({ typescript: true, jsx: true }).language;
    case 'html':
      return html().language;
    case 'css':
      return css().language;
    case 'py':
    case 'python':
      return python().language;
    case 'md':
    case 'markdown':
    case 'mdown':
    case 'mkd':
      return markdown().language;
    case 'heex':
    case 'eex':
    case 'leex':
      return html().language;
    default:
      return LanguageDescription.matchLanguageName(languages, normalized, true);
  }
}

const normalizeFileName = (filePath: string) => filePath.split('/').pop()?.toLowerCase() ?? '';

const matchLanguageDescriptionForFile = (filePath: string): LanguageDescription | null => {
  const filename = normalizeFileName(filePath);
  if (!filename) {
    return null;
  }
  return LanguageDescription.matchFilename(languages, filename);
};

const markdownHighlight = () => syntaxHighlighting(HighlightStyle.define([
  { tag: [t.heading1, t.heading2, t.heading3, t.heading4, t.heading5, t.heading6], fontWeight: '600' },
  { tag: t.strong, fontWeight: '600' },
  { tag: t.emphasis, fontStyle: 'italic' },
  { tag: t.strikethrough, textDecoration: 'line-through' },
  { tag: [t.link, t.url], color: 'var(--markdown-link, currentColor)', textDecoration: 'underline' },
  { tag: t.monospace, color: 'var(--markdown-inline-code, currentColor)', backgroundColor: 'var(--markdown-inline-code-bg, transparent)' },
  { tag: t.quote, color: 'var(--markdown-blockquote, currentColor)', fontStyle: 'italic' },
  { tag: t.list, color: 'color-mix(in srgb, var(--muted-foreground) 40%, var(--foreground) 60%)' },
  { tag: t.heading, color: 'var(--markdown-heading1, currentColor)' },
]));

export function languageByExtension(filePath: string): Extension | null {
  const normalized = filePath.toLowerCase();
  const filename = normalizeFileName(normalized);

  // Special filenames
  switch (filename) {
    case 'makefile':
    case 'gnumakefile':
      // No dedicated mode; shell is a decent fallback for Make-ish files.
      return shellLanguage;
  }

  const idx = normalized.lastIndexOf('.');
  const ext = idx >= 0 ? normalized.slice(idx + 1) : '';

  switch (ext) {
    // JavaScript/TypeScript (most common — keep static)
    case 'ts':
    case 'tsx':
    case 'mts':
    case 'cts':
      return javascript({ typescript: true, jsx: ext === 'tsx' });
    case 'js':
    case 'jsx':
    case 'mjs':
    case 'cjs':
      return javascript({ typescript: false, jsx: ext === 'jsx' });

    // Web (keep static)
    case 'json':
    case 'jsonc':
    case 'json5':
    case 'jsonl':
    case 'ndjson':
    case 'geojson':
      return json();
    case 'css':
    case 'scss':
    case 'sass':
    case 'less':
      return css();
    case 'html':
    case 'htm':
    case 'xml':
    case 'drawio':
    case 'dio':
      return html();
    case 'md':
    case 'mdx':
    case 'markdown':
    case 'mdown':
    case 'mkd':
      return [
        markdown({
          codeLanguages: codeBlockLanguageResolver,
        }),
        markdownHighlight(),
      ];

    // Shell (keep static)
    case 'sh':
    case 'bash':
    case 'zsh':
    case 'fish':
    case 'env':
      return shellLanguage;

    // Python (very common — keep static)
    case 'py':
    case 'pyw':
    case 'pyi':
      return python();

    // Less common languages: return null so callers fall back to
    // loadLanguageByExtension which dynamically imports from @codemirror/language-data.
    default:
      return null;
  }
}

export async function loadLanguageByExtension(filePath: string): Promise<Extension | null> {
  const description = matchLanguageDescriptionForFile(filePath);
  if (!description) {
    return null;
  }

  if (description.support) {
    return description.support;
  }

  try {
    return await description.load();
  } catch {
    return null;
  }
}
