/**
 * Theme Builder — Type panel (docs/chart-theming-scope.md §5).
 *
 * The font family (written into every font slot via `applyDraftFont`, the one
 * transform that walks the whole config) plus the axis type scale a brand tunes:
 * axis title and label size/weight. The title block (size, weight, anchor,
 * colour, subtitle) lives in its own Title panel so every title property sits
 * together; legend type lives in the Legend panel. Colour of text lives in the
 * per-domain panels (Axes, Legend, Headers); this panel is family, size, weight.
 */

import { useMemo, useRef } from 'react';
import { THEME_FONT_OPTIONS } from '@core/custom-theme';
import { type FontAxis, fontFamilyStack, isVariableFont } from '@core/font-asset';
import type { JsonObject } from '@core/spec-config';
import { humanizeBytes } from '@core/storage-estimate';
import { asNumber, type ConfigPath, countSet, getConfigValue } from '@core/theme-controls';
import { removeFont, uploadFontFiles } from '../services/fonts';
import { confirm } from '../stores/ConfirmStore';
import { useConfigSetter, useCustomThemeStore } from '../stores/CustomThemeStore';
import { useFontStore } from '../stores/FontStore';
import { Button } from './Button';
import { SelectControl, type SelectControlOption } from './SelectControl';
import { Accordion, type AccordionSection, NumberRow, WeightRow } from './ThemeFields';
import styles from './ThemeFields.module.css';

const FONT: ConfigPath = ['font'];
const AXIS_TITLE_SIZE: ConfigPath = ['axis', 'titleFontSize'];
const AXIS_TITLE_WEIGHT: ConfigPath = ['axis', 'titleFontWeight'];
const AXIS_LABEL_SIZE: ConfigPath = ['axis', 'labelFontSize'];
const AXIS_LABEL_WEIGHT: ConfigPath = ['axis', 'labelFontWeight'];

/**
 * The built-in roster, each labelled in its own family so the dropdown previews
 * the typeface (the type analogue of the color dropdowns' swatches). Loaded
 * before render by the chart-renderer's font gate. User-uploaded fonts are merged
 * ahead of these at render (see `TypeControls`).
 */
const ROSTER_FONT_OPTIONS: SelectControlOption<string>[] = THEME_FONT_OPTIONS.map(
  ({ value, label }) => ({ value, label, labelStyle: { fontFamily: value } }),
);

/** A variable font's badge text — its weight range when present, else just "Variable". */
function variableBadge(axes: FontAxis[] | undefined): string {
  const wght = axes?.find((a) => a.tag === 'wght');
  return wght ? `Variable ${wght.min}–${wght.max}` : 'Variable';
}

export function TypeControls({ config }: { config: JsonObject }) {
  const set = useConfigSetter();
  const fonts = useFontStore((s) => s.fonts);
  const fileRef = useRef<HTMLInputElement>(null);

  // User uploads first, then a divider, then the built-in roster — so a brand's
  // own faces lead the list (SelectControl's `dividerBefore` draws the boundary).
  const fontOptions = useMemo<SelectControlOption<string>[]>(() => {
    const userOptions = fonts.map((f) => ({
      value: fontFamilyStack(f.family),
      label: f.family,
      labelStyle: { fontFamily: fontFamilyStack(f.family) },
    }));
    const roster = ROSTER_FONT_OPTIONS.map((o, i) =>
      i === 0 && userOptions.length > 0 ? { ...o, dividerBefore: true } : o,
    );
    return [...userOptions, ...roster];
  }, [fonts]);

  const currentFont =
    typeof config.font === 'string' ? fontOptions.find((o) => o.value === config.font) : undefined;

  const onPickFiles = (e: React.ChangeEvent<HTMLInputElement>) => {
    const picked = e.target.files;
    if (picked && picked.length > 0) void uploadFontFiles(picked);
    e.target.value = ''; // let the same file be re-picked after a removal
  };

  const onRemoveFont = async (id: number, family: string) => {
    const ok = await confirm({
      title: 'Remove font',
      message:
        `Remove "${family}"? Charts using it fall back to a default font. ` +
        'This only removes the uploaded file from Astrolabe.',
      confirmLabel: 'Remove',
      danger: true,
    });
    if (ok) removeFont(id);
  };

  const sections: AccordionSection[] = [
    {
      id: 'font',
      title: 'Font family',
      hint: 'Applied to every text slot in the config.',
      badge: countSet(config, [FONT]),
      children: (
        <>
          <div className={styles.field}>
            <span className={styles.fieldLabel}>Font</span>
            <div className={styles.control}>
              <SelectControl
                id="theme-builder-font"
                label="Font family"
                heading="Apply font"
                options={fontOptions}
                value={currentFont?.value}
                onSelect={(family) => useCustomThemeStore.getState().applyDraftFont(family)}
                triggerContent={
                  <>
                    <span style={currentFont ? { fontFamily: currentFont.value } : undefined}>
                      {currentFont?.label ?? 'Apply font…'}
                    </span>
                    <span className={styles.caret} aria-hidden="true">
                      ▾
                    </span>
                  </>
                }
                triggerTitle="Write one font family into every font slot of the config"
              />
              <Button variant="ghost" onClick={() => fileRef.current?.click()}>
                Upload font…
              </Button>
              <input
                ref={fileRef}
                type="file"
                accept=".woff2,.woff,.ttf,.otf"
                multiple
                hidden
                onChange={onPickFiles}
              />
            </div>
          </div>
          {fonts.length > 0 && (
            <ul className={styles.fontList}>
              {fonts.map((f) => (
                <li key={f.id} className={styles.fontItem}>
                  <span className={styles.fontNameWrap}>
                    <span
                      className={styles.fontName}
                      style={{ fontFamily: fontFamilyStack(f.family) }}
                    >
                      {f.family}
                    </span>
                    {isVariableFont(f.axes) && (
                      <span className={styles.fontBadge}>{variableBadge(f.axes)}</span>
                    )}
                  </span>
                  <span className={styles.fontMeta}>{humanizeBytes(f.size)}</span>
                  <Button
                    variant="ghost"
                    onClick={() => void onRemoveFont(f.id, f.family)}
                    aria-label={`Remove ${f.family}`}
                  >
                    Remove
                  </Button>
                </li>
              ))}
            </ul>
          )}
        </>
      ),
    },
    {
      id: 'axisTitles',
      title: 'Axis titles',
      badge: countSet(config, [AXIS_TITLE_SIZE, AXIS_TITLE_WEIGHT]),
      children: (
        <>
          <NumberRow
            label="Size"
            name="Axis title size"
            value={asNumber(getConfigValue(config, AXIS_TITLE_SIZE))}
            min={0}
            unit="px"
            onChange={(n) => set(AXIS_TITLE_SIZE, n)}
          />
          <WeightRow
            id="type-axis-title-weight"
            label="Weight"
            name="Axis title weight"
            value={getConfigValue(config, AXIS_TITLE_WEIGHT)}
            onChange={(w) => set(AXIS_TITLE_WEIGHT, w)}
          />
        </>
      ),
    },
    {
      id: 'axisLabels',
      title: 'Axis labels',
      badge: countSet(config, [AXIS_LABEL_SIZE, AXIS_LABEL_WEIGHT]),
      children: (
        <>
          <NumberRow
            label="Size"
            name="Axis label size"
            value={asNumber(getConfigValue(config, AXIS_LABEL_SIZE))}
            min={0}
            unit="px"
            onChange={(n) => set(AXIS_LABEL_SIZE, n)}
          />
          <WeightRow
            id="type-axis-label-weight"
            label="Weight"
            name="Axis label weight"
            value={getConfigValue(config, AXIS_LABEL_WEIGHT)}
            onChange={(w) => set(AXIS_LABEL_WEIGHT, w)}
          />
        </>
      ),
    },
  ];

  return <Accordion sections={sections} idPrefix="type" />;
}
