import { useMemo, useState, type ReactNode } from 'react';
import {
  applyIntent,
  activeIntent,
  buildChartSpec,
  CHART_INTENTS,
  defaultBuilderConfig,
  defaultFieldType,
  intentApplicable,
  type BuilderConfig,
  type ChannelMapping,
  type ChannelName,
  type ChartIntent,
  type MarkType,
} from '@core/chart-builder';
import { CHART_EXAMPLES } from '@core/examples';
import {
  chartConfigForSelection,
  customThemeSelection,
  type ChartThemeSelection,
} from '@core/vega-themes';
import { THEME_PREVIEW_SPECS } from '@core/theme-preview-specs';
import {
  SAMPLE_DATASET_COLUMNS,
  SAMPLE_DATASET_NAME,
  SAMPLE_DATASET_ROWS,
} from '@core/sample-dataset';
import { DEMO_CUSTOM_THEMES } from './demo-themes';
import { LandingChart } from './LandingChart';
import styles from './Landing.module.css';

// TODO: import { UiTheme } from '@core/theme' instead of redeclaring it — the
// learn entry already uses the canonical one.
type UiTheme = 'light' | 'dark';

// Minimal JSON syntax highlighter for the read-only spec view: keys, strings,
// numbers, and literals get a token class; punctuation and whitespace pass
// through untouched. A read-only marketing snippet doesn't warrant a highlighter
// dependency. `matchAll` over a global regex is stateless per call.
const JSON_TOKEN =
  /("(?:[^"\\]|\\.)*"(?=\s*:))|("(?:[^"\\]|\\.)*")|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|\b(?:true|false|null)\b/g;

function JsonCode({ spec }: { spec: unknown }): ReactNode {
  const text = JSON.stringify(spec, null, 2);
  const out: ReactNode[] = [];
  let last = 0;
  let key = 0;
  for (const m of text.matchAll(JSON_TOKEN)) {
    const idx = m.index ?? 0;
    if (idx > last) out.push(text.slice(last, idx));
    const cls = m[1] ? styles.tokKey : m[2] ? styles.tokStr : styles.tokNum;
    out.push(
      <span key={key++} className={cls}>
        {m[0]}
      </span>,
    );
    last = idx + m[0].length;
  }
  if (last < text.length) out.push(text.slice(last));
  return <pre className={styles.code}>{out}</pre>;
}

/** A snippet-style filename from an example name. */
function specFilename(name: string): string {
  return `${name
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')}.vl.json`;
}

/** The mark name of an example spec (string mark or `{ type }` form). */
function markLabel(spec: Record<string, unknown>): string {
  const mark = spec.mark;
  if (typeof mark === 'string') return mark;
  if (mark && typeof mark === 'object' && 'type' in mark) return String(mark.type);
  return 'chart';
}

function Brand(): ReactNode {
  return (
    <span className={styles.brand}>
      <img className={styles.brandMark} src="/favicon.svg" alt="" width={20} height={20} />{' '}
      Astrolabe
    </span>
  );
}

// ── Hero: switch snippets, the editor + chart follow ────────────────────────────

function HeroAppWindow({ theme }: { theme: UiTheme }): ReactNode {
  const [selected, setSelected] = useState(0);
  const example = CHART_EXAMPLES[selected];
  const config = useMemo(() => chartConfigForSelection('astrolabe', theme), [theme]);

  return (
    <div className={styles.appWin}>
      <div className={styles.appWinBar}>
        <span className={styles.dots}>
          <i />
          <i />
          <i />
        </span>
        <span className={styles.grow} />
        <span className={styles.pill}>{CHART_EXAMPLES.length} snippets</span>
        <span className={styles.pill}>live preview</span>
      </div>
      <div className={styles.appBody}>
        <div className={styles.col}>
          <div className={styles.colHead}>Library</div>
          <div className={styles.libSearch}>Search snippets…</div>
          <div className={styles.libList}>
            {CHART_EXAMPLES.map((ex, i) => (
              <button
                key={ex.id}
                type="button"
                className={`${styles.row} ${i === selected ? styles.rowSel : ''}`}
                onClick={() => setSelected(i)}
                aria-current={i === selected ? 'true' : undefined}
              >
                <div className={styles.rowName}>{ex.name}</div>
                <div className={styles.rowMeta}>
                  <span className={styles.chip}>{markLabel(ex.spec)}</span>
                </div>
              </button>
            ))}
          </div>
        </div>
        <div className={styles.col}>
          <div className={styles.colHead}>{specFilename(example.name)}</div>
          <JsonCode spec={example.spec} />
        </div>
        <div className={styles.col}>
          <div className={styles.colHead}>Preview</div>
          <div className={styles.preview}>
            <LandingChart spec={example.spec} config={config} />
          </div>
        </div>
      </div>
    </div>
  );
}

// ── Chart Builder demo: pick fields, a mark, or an intent; the chart rebuilds ────

const MARK_OPTIONS: ReadonlyArray<{ type: MarkType; label: string }> = [
  { type: 'bar', label: 'Bar' },
  { type: 'line', label: 'Line' },
  { type: 'point', label: 'Point' },
  { type: 'area', label: 'Area' },
];

const INTENT_LABELS: Record<ChartIntent, string> = {
  compare: 'Compare',
  ranking: 'Ranking',
  time: 'Over time',
  correlation: 'Correlation',
  distribution: 'Distribution',
  partToWhole: 'Part-to-whole',
  heatmap: 'Heatmap',
};

const BUILDER_TYPES = SAMPLE_DATASET_COLUMNS.columnTypes;
const ALL_COLUMNS = SAMPLE_DATASET_COLUMNS.columns;
const MEASURE_COLUMNS = BUILDER_TYPES.filter((c) => c.type === 'number').map((c) => c.name);
const CATEGORY_COLUMNS = BUILDER_TYPES.filter(
  (c) => c.type === 'string' || c.type === 'boolean',
).map((c) => c.name);
const COUNT = '__count__';
const NONE = '__none__';

function columnType(name: string): (typeof BUILDER_TYPES)[number]['type'] {
  return BUILDER_TYPES.find((c) => c.name === name)?.type ?? 'string';
}
/** A field mapping with the field type the builder would derive for that column. */
function fieldMapping(name: string): ChannelMapping {
  return { field: name, type: defaultFieldType(columnType(name)) };
}

function BuilderDemo({ theme }: { theme: UiTheme }): ReactNode {
  const columns = SAMPLE_DATASET_COLUMNS;
  const [config, setConfig] = useState<BuilderConfig>(() =>
    defaultBuilderConfig(SAMPLE_DATASET_NAME, columns),
  );
  const active = activeIntent(config, columns);
  const chartConfig = useMemo(() => chartConfigForSelection('astrolabe', theme), [theme]);
  const spec = useMemo(() => {
    const built = buildChartSpec(config);
    // Inline the sample rows so the spec renders standalone (the builder otherwise
    // emits a by-name dataset reference the app resolves from its library).
    built.data = { values: SAMPLE_DATASET_ROWS };
    return built;
  }, [config]);

  const setEncoding = (channel: ChannelName, mapping: ChannelMapping | null): void =>
    setConfig({ ...config, encodings: { ...config.encodings, [channel]: mapping } });
  const yValue =
    config.encodings.y?.aggregate === 'count' ? COUNT : (config.encodings.y?.field ?? COUNT);

  return (
    <>
      <div className={styles.seg}>
        {MARK_OPTIONS.map((m) => (
          <button
            key={m.type}
            type="button"
            aria-pressed={config.mark === m.type}
            className={config.mark === m.type ? styles.on : undefined}
            onClick={() => setConfig({ ...config, mark: m.type })}
          >
            {m.label}
          </button>
        ))}
      </div>
      <label className={styles.shelf}>
        <b>X</b>
        <select
          className={styles.select}
          value={config.encodings.x?.field ?? ''}
          onChange={(e) => setEncoding('x', fieldMapping(e.target.value))}
        >
          {ALL_COLUMNS.map((c) => (
            <option key={c} value={c}>
              {c}
            </option>
          ))}
        </select>
      </label>
      <label className={styles.shelf}>
        <b>Y</b>
        <select
          className={styles.select}
          value={yValue}
          onChange={(e) =>
            setEncoding(
              'y',
              e.target.value === COUNT
                ? { type: 'quantitative', aggregate: 'count' }
                : fieldMapping(e.target.value),
            )
          }
        >
          <option value={COUNT}>count of records</option>
          {MEASURE_COLUMNS.map((c) => (
            <option key={c} value={c}>
              {c}
            </option>
          ))}
        </select>
      </label>
      <label className={styles.shelf}>
        <b>Color</b>
        <select
          className={styles.select}
          value={config.encodings.color?.field ?? NONE}
          onChange={(e) =>
            setEncoding('color', e.target.value === NONE ? null : fieldMapping(e.target.value))
          }
        >
          <option value={NONE}>none</option>
          {CATEGORY_COLUMNS.map((c) => (
            <option key={c} value={c}>
              {c}
            </option>
          ))}
        </select>
      </label>
      <div className={styles.intent}>
        {CHART_INTENTS.map((intent) => (
          <button
            key={intent}
            type="button"
            disabled={!intentApplicable(intent, columns)}
            aria-pressed={active === intent}
            className={active === intent ? styles.intentOn : undefined}
            onClick={() => setConfig(applyIntent(config, intent, columns))}
          >
            {INTENT_LABELS[intent]}
          </button>
        ))}
      </div>
      <LandingChart spec={spec} config={chartConfig} />
    </>
  );
}

// ── Theme Builder demo: pick a theme, the gallery re-renders ─────────────────────

const THEME_CHOICES: ReadonlyArray<{ selection: ChartThemeSelection; label: string }> = [
  ...DEMO_CUSTOM_THEMES.map((t) => ({ selection: customThemeSelection(t.id), label: t.label })),
  { selection: 'astrolabe', label: 'Astrolabe' },
  { selection: 'fivethirtyeight', label: 'FiveThirtyEight' },
  { selection: 'vox', label: 'Vox' },
  { selection: 'dark', label: 'Vega Dark' },
];
const GALLERY = THEME_PREVIEW_SPECS.filter((s) => ['bar', 'line', 'scatter'].includes(s.id));

function ThemeDemo({ theme }: { theme: UiTheme }): ReactNode {
  const [selection, setSelection] = useState<ChartThemeSelection>(customThemeSelection(1));
  const config = useMemo(
    () => chartConfigForSelection(selection, theme, DEMO_CUSTOM_THEMES),
    [selection, theme],
  );

  return (
    <>
      <div className={styles.intent}>
        {THEME_CHOICES.map((choice) => (
          <button
            key={choice.selection}
            type="button"
            aria-pressed={selection === choice.selection}
            className={selection === choice.selection ? styles.intentOn : undefined}
            onClick={() => setSelection(choice.selection)}
          >
            {choice.label}
          </button>
        ))}
      </div>
      <div className={styles.gallery}>
        {GALLERY.map((ps) => (
          <div key={ps.id} className={styles.mini}>
            <LandingChart spec={ps.spec} config={config} className={styles.miniHost} />
          </div>
        ))}
      </div>
    </>
  );
}

// ── Page ─────────────────────────────────────────────────────────────────────

export function Landing(): ReactNode {
  const [theme, setTheme] = useState<UiTheme>('light');

  function toggleTheme(): void {
    const next = theme === 'dark' ? 'light' : 'dark';
    setTheme(next);
    document.documentElement.dataset.theme = next;
  }

  return (
    <div className={styles.landing}>
      <nav className={styles.nav}>
        <div className={`${styles.wrap} ${styles.navIn}`}>
          <Brand />
          <span className={styles.navSpacer} />
          <a className={styles.navLink} href="#author">
            Authoring
          </a>
          <a className={styles.navLink} href="#library">
            Library
          </a>
          <a className={styles.navLink} href="#theme">
            Theming
          </a>
          <a className={styles.navLink} href="/learn/">
            Deep dives
          </a>
          <button className={`${styles.btn} ${styles.btnSm}`} type="button" onClick={toggleTheme}>
            {theme === 'dark' ? 'Light' : 'Dark'}
          </button>
          <a className={`${styles.btn} ${styles.btnSm} ${styles.navCta}`} href="/app/">
            Open Astrolabe →
          </a>
        </div>
      </nav>

      <header className={styles.hero}>
        <div className={styles.wrap}>
          <h1>
            A home for your <b>Vega-Lite charts.</b>
          </h1>
          <p>
            Astrolabe is a local studio for Vega-Lite. Write a spec by hand or build one by
            clicking, give it a theme, and keep all your charts in one searchable library.
          </p>
          <div className={styles.heroCta}>
            <a className={`${styles.btn} ${styles.btnPrimary}`} href="/app/">
              Open Astrolabe
            </a>
            <span className={styles.heroNote}>Free, no account, works offline</span>
          </div>
        </div>
      </header>

      <section className={styles.stage}>
        <div className={styles.wrap}>
          <HeroAppWindow theme={theme} />
          <p className={styles.stageHint}>
            Pick a snippet on the left — the editor and chart follow.
          </p>
        </div>
      </section>

      <div className={styles.wrap}>
        <section className={styles.cap} id="author">
          <div className={styles.capText}>
            <div className={styles.capEyebrow}>Two ways in</div>
            <h2 className={styles.capH}>
              Write the spec, or <b>build it by clicking.</b>
            </h2>
            <p className={styles.capP}>
              The editor is Monaco with the Vega-Lite schema loaded, so you get validation,
              autocompletion, and inline docs without going online. Each chart keeps an editable
              draft alongside a published version you can revert to.
            </p>
            <p className={styles.capP}>
              If you'd rather not start from JSON, the builder works from the kind of chart you
              want. Pick fields, a mark, or a whole intent — it writes the spec for you. Try it:
            </p>
          </div>
          <div className={styles.shot}>
            <div className={styles.shotBar}>
              <span className={styles.dots}>
                <i />
                <i />
                <i />
              </span>{' '}
              Chart Builder · {SAMPLE_DATASET_NAME}
            </div>
            <div className={styles.shotBody}>
              <BuilderDemo theme={theme} />
            </div>
          </div>
        </section>

        <section className={`${styles.cap} ${styles.capRev}`} id="library">
          <div className={styles.capText}>
            <div className={styles.capEyebrow}>One library</div>
            <h2 className={styles.capH}>
              Every chart you make is <b>saved</b>, with the data behind it.
            </h2>
            <p className={styles.capP}>
              Store a dataset once and reference it by name from as many charts as you want. Rename
              it and Astrolabe updates every chart that used it.
            </p>
            <p className={styles.capP}>
              Load data by pasting CSV or JSON, or by fetching a URL. You can also lift inline data
              out of a spec into a shared dataset. Search and sort as the collection grows, and
              duplicate a snippet to start a variant.
            </p>
          </div>
          <div className={styles.shot}>
            <div className={styles.shotBar}>
              <span className={styles.dots}>
                <i />
                <i />
                <i />
              </span>{' '}
              Datasets
            </div>
            <div className={`${styles.shotBody} ${styles.shotBodyFlush}`}>
              <div className={styles.row}>
                <div className={styles.rowName}>
                  sales-2025 <span className={styles.rowSub}>· CSV · 1,204 rows</span>
                </div>
                <div className={styles.rowMeta}>
                  used by <span className={styles.chip}>4 snippets</span> · quarter, region,
                  revenue, units
                </div>
              </div>
              <div className={styles.row}>
                <div className={styles.rowName}>
                  cohorts <span className={styles.rowSub}>· JSON · 96 rows</span>
                </div>
                <div className={styles.rowMeta}>
                  used by <span className={styles.chip}>1 snippet</span> · cohort, week, retained
                </div>
              </div>
              <div className={styles.row}>
                <div className={styles.rowName}>
                  events <span className={styles.rowSub}>· URL · refreshed today</span>
                </div>
                <div className={styles.rowMeta}>
                  used by <span className={styles.chip}>3 snippets</span> · day, type, count
                </div>
              </div>
              <div className={`${styles.row} ${styles.rowLast}`}>
                <div className={styles.rowName}>
                  survey <span className={styles.rowSub}>· CSV · 512 rows</span>
                </div>
                <div className={styles.rowMeta}>
                  used by <span className={styles.chip}>2 snippets</span> · score, segment
                </div>
              </div>
            </div>
          </div>
        </section>

        <section className={styles.cap} id="theme">
          <div className={styles.capText}>
            <div className={styles.capEyebrow}>Make it yours</div>
            <h2 className={styles.capH}>
              Give your charts a <b>look of their own.</b>
            </h2>
            <p className={styles.capP}>
              Sixteen presets to start from, or build your own in the Theme Builder — colour, type,
              axes, legend, and layout. The first three here are built from scratch. Try a few:
            </p>
            <p className={styles.capP}>
              Upload your own fonts, variable fonts included, and apply one across a whole theme at
              once. Save the theme and use it on any chart.
            </p>
          </div>
          <div className={styles.shot}>
            <div className={styles.shotBar}>
              <span className={styles.dots}>
                <i />
                <i />
                <i />
              </span>{' '}
              Theme Builder
            </div>
            <div className={styles.shotBody}>
              <ThemeDemo theme={theme} />
            </div>
          </div>
        </section>

        <section className={`${styles.cap} ${styles.capRev}`} id="export">
          <div className={styles.capText}>
            <div className={styles.capEyebrow}>Exporting</div>
            <h2 className={styles.capH}>
              Save a chart in the <b>format you need.</b>
            </h2>
            <p className={styles.capP}>
              Export as a PNG at 1–3×, as an SVG, or as a Vega-Lite spec. The spec can carry its
              data inline, so the file renders on its own wherever it lands.
            </p>
            <p className={styles.capP}>
              Your whole workspace exports and re-imports as a single JSON file.
            </p>
          </div>
          <div className={styles.shot}>
            <div className={styles.shotBar}>
              <span className={styles.dots}>
                <i />
                <i />
                <i />
              </span>{' '}
              Export chart
            </div>
            <div className={styles.shotBody}>
              <div className={styles.exp}>
                <div className={styles.expRow}>
                  <span className={styles.expLab}>Format</span>
                  <span className={styles.grp}>
                    <button type="button" className={styles.on}>
                      PNG
                    </button>
                    <button type="button">SVG</button>
                    <button type="button">Spec</button>
                  </span>
                </div>
                <div className={styles.expRow}>
                  <span className={styles.expLab}>Scale</span>
                  <span className={styles.grp}>
                    <button type="button">1×</button>
                    <button type="button" className={styles.on}>
                      2×
                    </button>
                    <button type="button">3×</button>
                  </span>
                </div>
                <div className={styles.expRow}>
                  <span className={styles.expLab}>Background</span>
                  <span className={styles.grp}>
                    <button type="button" className={styles.on}>
                      Theme
                    </button>
                    <button type="button">White</button>
                    <button type="button">None</button>
                  </span>
                </div>
                <div className={styles.expRow}>
                  <span className={styles.expLab}>Data</span>
                  <span className={styles.grp}>
                    <button type="button" className={styles.on}>
                      Inline
                    </button>
                    <button type="button">Keep refs</button>
                  </span>
                </div>
                <div className={styles.expDownload}>
                  <button
                    type="button"
                    className={`${styles.btn} ${styles.btnSm} ${styles.btnFill}`}
                  >
                    Download .png
                  </button>
                </div>
              </div>
            </div>
          </div>
        </section>
      </div>

      <section className={styles.creed}>
        <div className={styles.wrap}>
          <h2>
            Your charts are <b>yours.</b>
          </h2>
          <p className={styles.creedLede}>
            Astrolabe runs in your browser, and your charts and data stay there — no account, no
            server, no AI behind it to send them to — so work that has to stay confidential is safe
            here from the first chart. And what it makes is ordinary Vega-Lite JSON: read it, edit
            it in other tools, or take it elsewhere whenever you want.
          </p>
          <div className={styles.creedGrid}>
            <div>
              <span className={styles.creedKey}>private</span>
              <h3>Local to your browser</h3>
              <p>
                Charts, data, and themes are saved in this browser and nowhere else — there is no
                server behind Astrolabe to receive them. It keeps working offline once installed.
              </p>
            </div>
            <div>
              <span className={styles.creedKey}>portable</span>
              <h3>Ordinary Vega-Lite</h3>
              <p>
                Export a single chart or the whole library as standard JSON and open it in any other
                Vega tool.
              </p>
            </div>
            <div>
              <span className={styles.creedKey}>yours</span>
              <h3>Set up your way</h3>
              <p>
                Author by hand or by clicking, with your own fonts and themes. Arrange the library
                to match how you work.
              </p>
            </div>
          </div>
        </div>
      </section>

      <section className={styles.close}>
        <div className={styles.wrap}>
          <h2>
            Open it and start a <b>chart.</b>
          </h2>
          <a className={`${styles.btn} ${styles.btnPrimary}`} href="/app/">
            Open Astrolabe
          </a>
        </div>
      </section>

      <footer className={styles.footer}>
        <div className={`${styles.wrap} ${styles.footerIn}`}>
          <Brand />
          <span className={styles.footerSp} />
          <a href="/app/">Open the app</a>
          <a href="/learn/">Deep dives</a>
          <a href="https://vega.github.io/vega-lite/">Built on Vega-Lite</a>
        </div>
      </footer>
    </div>
  );
}
