/**
 * Theme Builder — Color panel (docs/chart-theming-scope.md §5).
 *
 * Structured controls over the draft config's color surface: the categorical
 * palette (`range.category`), the default single-series mark color
 * (`mark.color`), and the sequential/diverging gradients (`range.heatmap`+`ramp`
 * and `range.diverging`). Each writes through `mutateDraftConfig` so the JSON
 * and the gallery follow; reads come from the parsed draft config, so a JSON
 * hand-edit reflects straight back into the controls.
 *
 * Color model (§5): every family holds either a named Vega scheme as the
 * range-scheme object `{ scheme: name }` (compact, the quick path) or an explicit
 * color array (custom tuning). Picking a scheme from the preview-bearing dropdown
 * writes that object (a bare string is rejected by Vega at render); "Materialize"
 * expands it to an editable array of swatches — categorical, sequential, and
 * diverging alike.
 * Each swatch pairs the native color picker with a hex text field, so a value
 * can be read, copied, and retyped anywhere.
 */

import type { ReactNode } from 'react';
import type { JsonObject } from '@core/spec-config';
import {
  type ConfigPath,
  countSet,
  getConfigValue,
  schemeColors,
  schemesByKind,
} from '@core/theme-controls';
import { Button } from './Button';
import { ColorField } from './ColorField';
import { Icon } from './Icon';
import { IconButton } from './IconButton';
import { SelectControl, type SelectControlOption } from './SelectControl';
import { useConfigSetter } from '../stores/CustomThemeStore';
import { Accordion, type AccordionSection } from './ThemeFields';
import styles from './ColorControls.module.css';

const CATEGORY: ConfigPath = ['range', 'category'];
const MARK_COLOR: ConfigPath = ['mark', 'color'];
const HEATMAP: ConfigPath = ['range', 'heatmap'];
const RAMP: ConfigPath = ['range', 'ramp'];
const DIVERGING: ConfigPath = ['range', 'diverging'];

/** Vega-Lite's default mark color — shown as the starting value when unset. */
const VEGA_DEFAULT_MARK = '#4c78a8';
/** Seeded into a new swatch and the materialize-from-default paths. */
const NEW_SWATCH = '#888888';
const DEFAULT_CATEGORICAL = 'tableau10';
const DEFAULT_SEQUENTIAL = 'viridis';
const DEFAULT_DIVERGING = 'redblue';
/** Stops a materialized gradient starts with (continuous schemes are sampled). */
const GRADIENT_STOPS = 7;

const asArray = (v: unknown): string[] | null =>
  Array.isArray(v) && v.every((c) => typeof c === 'string') ? v : null;
const asString = (v: unknown): string | null => (typeof v === 'string' ? v : null);

/**
 * A named scheme is stored in a `range` family as Vega's range-scheme object
 * `{ scheme: name }`. A bare scheme-name string compiles but is rejected by Vega
 * at render ("Unrecognized scale range value: …"), blanking the chart — so reads
 * accept either the object or a legacy/hand-authored bare string, while writes
 * (`schemeRange`) always use the object form.
 */
const asScheme = (v: unknown): string | null => {
  if (typeof v === 'string') return v;
  if (v && typeof v === 'object' && typeof (v as { scheme?: unknown }).scheme === 'string') {
    return (v as { scheme: string }).scheme;
  }
  return null;
};
const schemeRange = (name: string): { scheme: string } => ({ scheme: name });

const gradientCss = (colors: string[]): string =>
  colors.length ? `linear-gradient(90deg, ${colors.join(', ')})` : 'transparent';

// Dropdown previews, built once: a swatch strip for categorical, a gradient bar
// for the continuous families.
const swatchStrip = (colors: string[]): ReactNode => (
  <span className={styles.optStrip}>
    {colors.slice(0, 12).map((c, i) => (
      <span key={i} className={styles.optSwatch} style={{ background: c }} />
    ))}
  </span>
);
const gradientBar = (colors: string[]): ReactNode => (
  <span className={styles.optGradient} style={{ background: gradientCss(colors) }} />
);

const CATEGORICAL_OPTIONS: SelectControlOption<string>[] = schemesByKind('categorical').map(
  (s) => ({
    value: s.name,
    label: s.label,
    preview: swatchStrip(schemeColors(s.name)),
  }),
);
const SEQUENTIAL_OPTIONS: SelectControlOption<string>[] = schemesByKind('sequential').map((s) => ({
  value: s.name,
  label: s.label,
  preview: gradientBar(schemeColors(s.name, 12)),
}));
const DIVERGING_OPTIONS: SelectControlOption<string>[] = schemesByKind('diverging').map((s) => ({
  value: s.name,
  label: s.label,
  preview: gradientBar(schemeColors(s.name, 12)),
}));

/** A ColorField swatch with a hover/focus-revealed remove, for editable lists. */
function SwatchRow({
  color,
  label,
  onChange,
  onRemove,
}: {
  color: string;
  label: string;
  onChange: (hex: string) => void;
  onRemove?: () => void;
}) {
  return (
    <span className={styles.swatchUnit}>
      <ColorField value={color} label={label} onChange={onChange} hex />
      {onRemove && (
        <IconButton
          size="sm"
          label={`Remove ${label}`}
          className={styles.swatchRemove}
          onClick={onRemove}
        >
          <Icon name="close" />
        </IconButton>
      )}
    </span>
  );
}

export function ColorControls({ config }: { config: JsonObject }) {
  const set = useConfigSetter();
  // Sequential color lives in two slots (heatmaps + continuous legends); keep them together.
  const setSeq = (value: unknown) => {
    set(HEATMAP, value);
    set(RAMP, value);
  };

  const catValue = getConfigValue(config, CATEGORY);
  const catArray = asArray(catValue);
  const catScheme = asScheme(catValue);

  const markColor = asString(getConfigValue(config, MARK_COLOR));

  const seqValue = getConfigValue(config, HEATMAP);
  const seqArray = asArray(seqValue);
  const seqScheme = asScheme(seqValue);

  const divValue = getConfigValue(config, DIVERGING);
  const divArray = asArray(divValue);
  const divScheme = asScheme(divValue);

  /** Stops to drive a gradient preview for a family in any of its states. */
  const previewStops = (array: string[] | null, scheme: string | null): string[] =>
    array ?? (scheme ? schemeColors(scheme, 9) : []);

  const sections: AccordionSection[] = [
    {
      id: 'categorical',
      title: 'Categorical palette',
      hint: 'Series colors — assigned to discrete categories in order.',
      badge: countSet(config, [CATEGORY]),
      children: (
        <>
          <div className={styles.row}>
            <SelectControl
              id="color-categorical-scheme"
              label="Categorical color scheme"
              heading="Color scheme"
              options={CATEGORICAL_OPTIONS}
              value={catScheme ?? undefined}
              onSelect={(name) => set(CATEGORY, schemeRange(name))}
              triggerContent={
                <>
                  <span className={styles.triggerPreview} aria-hidden="true">
                    {(catArray ?? (catScheme ? schemeColors(catScheme) : []))
                      .slice(0, 6)
                      .map((c, i) => (
                        <span key={i} className={styles.optSwatch} style={{ background: c }} />
                      ))}
                  </span>
                  <span>
                    {catArray ? `Custom (${catArray.length})` : (catScheme ?? 'Theme default')}
                  </span>
                  <span className={styles.caret} aria-hidden="true">
                    ▾
                  </span>
                </>
              }
              triggerTitle="Pick a named palette"
            />
            {catArray ? (
              <Button variant="ghost" onClick={() => set(CATEGORY, [...catArray, NEW_SWATCH])}>
                Add color
              </Button>
            ) : (
              <Button
                variant="ghost"
                onClick={() => set(CATEGORY, schemeColors(catScheme ?? DEFAULT_CATEGORICAL))}
              >
                Materialize to edit
              </Button>
            )}
          </div>

          {catArray && (
            <div className={styles.swatches}>
              {catArray.map((color, i) => (
                <SwatchRow
                  key={i}
                  color={color}
                  label={`Palette color ${i + 1}`}
                  onChange={(hex) =>
                    set(
                      CATEGORY,
                      catArray.map((c, j) => (j === i ? hex : c)),
                    )
                  }
                  onRemove={() =>
                    set(
                      CATEGORY,
                      catArray.length === 1 ? undefined : catArray.filter((_, j) => j !== i),
                    )
                  }
                />
              ))}
            </div>
          )}
        </>
      ),
    },
    {
      id: 'markColor',
      title: 'Default mark color',
      hint: 'Single-series fill — bars, points, and lines with no color encoding.',
      badge: countSet(config, [MARK_COLOR]),
      children: (
        <>
          <div className={styles.row}>
            <SwatchRow
              color={markColor || VEGA_DEFAULT_MARK}
              label="Default mark color"
              onChange={(hex) => set(MARK_COLOR, hex)}
            />
            {markColor ? (
              <Button variant="ghost" onClick={() => set(MARK_COLOR, undefined)}>
                Clear
              </Button>
            ) : (
              <span className={styles.hint}>Unset — Vega default ({VEGA_DEFAULT_MARK})</span>
            )}
          </div>
        </>
      ),
    },
    {
      id: 'sequential',
      title: 'Sequential gradient',
      hint: 'Continuous color — heatmaps and quantitative legends.',
      badge: countSet(config, [HEATMAP]),
      children: (
        <>
          <div className={styles.row}>
            <span
              className={styles.gradientPreview}
              aria-hidden="true"
              style={{ background: gradientCss(previewStops(seqArray, seqScheme)) }}
            />
            <SelectControl
              id="color-sequential-scheme"
              label="Sequential color scheme"
              heading="Sequential scheme"
              options={SEQUENTIAL_OPTIONS}
              value={seqScheme ?? undefined}
              onSelect={(name) => setSeq(schemeRange(name))}
              triggerContent={
                <>
                  <span>
                    {seqArray ? `Custom (${seqArray.length})` : (seqScheme ?? 'Theme default')}
                  </span>
                  <span className={styles.caret} aria-hidden="true">
                    ▾
                  </span>
                </>
              }
            />
            {seqArray ? (
              <Button variant="ghost" onClick={() => setSeq([...seqArray, NEW_SWATCH])}>
                Add stop
              </Button>
            ) : (
              <Button
                variant="ghost"
                onClick={() =>
                  setSeq(schemeColors(seqScheme ?? DEFAULT_SEQUENTIAL, GRADIENT_STOPS))
                }
              >
                Materialize to edit
              </Button>
            )}
            {(seqArray || seqScheme) && (
              <Button variant="ghost" onClick={() => setSeq(undefined)}>
                Clear
              </Button>
            )}
          </div>
          {seqArray && (
            <div className={styles.swatches}>
              {seqArray.map((color, i) => (
                <SwatchRow
                  key={i}
                  color={color}
                  label={`Sequential stop ${i + 1}`}
                  onChange={(hex) => setSeq(seqArray.map((c, j) => (j === i ? hex : c)))}
                  onRemove={() =>
                    setSeq(seqArray.length === 1 ? undefined : seqArray.filter((_, j) => j !== i))
                  }
                />
              ))}
            </div>
          )}
        </>
      ),
    },
    {
      id: 'diverging',
      title: 'Diverging gradient',
      hint: 'Two-ended color — values around a meaningful midpoint.',
      badge: countSet(config, [DIVERGING]),
      children: (
        <>
          <div className={styles.row}>
            <span
              className={styles.gradientPreview}
              aria-hidden="true"
              style={{ background: gradientCss(previewStops(divArray, divScheme)) }}
            />
            <SelectControl
              id="color-diverging-scheme"
              label="Diverging color scheme"
              heading="Diverging scheme"
              options={DIVERGING_OPTIONS}
              value={divScheme ?? undefined}
              onSelect={(name) => set(DIVERGING, schemeRange(name))}
              triggerContent={
                <>
                  <span>
                    {divArray ? `Custom (${divArray.length})` : (divScheme ?? 'Theme default')}
                  </span>
                  <span className={styles.caret} aria-hidden="true">
                    ▾
                  </span>
                </>
              }
            />
            {divArray ? (
              <Button variant="ghost" onClick={() => set(DIVERGING, [...divArray, NEW_SWATCH])}>
                Add stop
              </Button>
            ) : (
              <Button
                variant="ghost"
                onClick={() =>
                  set(DIVERGING, schemeColors(divScheme ?? DEFAULT_DIVERGING, GRADIENT_STOPS))
                }
              >
                Materialize to edit
              </Button>
            )}
            {(divArray || divScheme) && (
              <Button variant="ghost" onClick={() => set(DIVERGING, undefined)}>
                Clear
              </Button>
            )}
          </div>
          {divArray && (
            <div className={styles.swatches}>
              {divArray.map((color, i) => (
                <SwatchRow
                  key={i}
                  color={color}
                  label={`Diverging stop ${i + 1}`}
                  onChange={(hex) =>
                    set(
                      DIVERGING,
                      divArray.map((c, j) => (j === i ? hex : c)),
                    )
                  }
                  onRemove={() =>
                    set(
                      DIVERGING,
                      divArray.length === 1 ? undefined : divArray.filter((_, j) => j !== i),
                    )
                  }
                />
              ))}
            </div>
          )}
        </>
      ),
    },
  ];

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