import { useEffect, useRef, useState } from 'react';
import { EventsEmit } from '../../wailsjs/runtime/runtime';
import { GripVertical, Lock } from 'lucide-react';
import { GetMatrixColors, GetRowColors, SaveMatrixColors, SaveRowColors } from '../../wailsjs/go/main/App';
import { Checkbox } from '@/components/ui/checkbox';
import { useI18n } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import type { RowColorSettings } from '@/lib/rowColors';
import {
  MATRIX_VARS, applyMatrixColors, effectiveMatrixColor, emptyMatrixColors,
  type MatrixColors,
} from '@/lib/matrixColors';

// A fixed palette plus a free picker. Muted values on purpose: they are
// composited at low opacity over a dark grid, where a saturated colour reads as
// an error state rather than a status.
const PALETTE = [
  '#16a34a', '#0ea5e9', '#f59e0b', '#a855f7',
  '#dc2626', '#14b8a6', '#eab308', '#ec4899',
  '#64748b', '#84cc16', '#6366f1', '#f97316',
];

// The rule ids the backend orders; the labels live here so a translation never
// travels through the settings row.
const LABELS: Record<string, string> = {
  to_send: 'appr.ruleToSend',
  confirmed: 'appr.ruleConfirmed',
  sent: 'appr.ruleSent',
  worked: 'appr.ruleWorked',
};

// The channels a rule can be scoped to. "worked" is the catch-all — it means
// nothing on ANY channel — so narrowing it would say nothing.
const CHANNELS = ['qsl', 'lotw', 'eqsl', 'qrz', 'hamlog'] as const;
const CHANNEL_LABELS: Record<string, string> = {
  qsl: 'appr.chQsl', lotw: 'LoTW', eqsl: 'eQSL', qrz: 'QRZ.com', hamlog: 'HAMLOG.online',
};

// MatrixColorsSection recolours the band/mode matrix — the PH/CW/DIG grid in the
// Stats panel.
//
// The pickers are seeded from what the matrix is painting RIGHT NOW (the active
// theme's ramp, or an existing override), not from a fixed palette: the operator
// starts from the colours in front of them and moves one, instead of being
// handed six values that may belong to a theme they stopped using. Every change
// is applied to the live document at once, so the sample row below is the real
// thing rather than a mock-up of it.
function MatrixColorsSection() {
  const { t } = useI18n();
  const [cfg, setCfg] = useState<MatrixColors | null>(null);

  useEffect(() => {
    (async () => {
      try {
        setCfg((await GetMatrixColors()) as any);
      } catch {
        setCfg(emptyMatrixColors());
      }
    })();
  }, []);

  const save = (next: MatrixColors) => {
    setCfg(next);
    applyMatrixColors(next); // live, before the round trip — the panel must not lag the choice
    SaveMatrixColors(next as any).catch(() => {});
  };

  // Turning it ON with nothing stored would change nothing at all and read as a
  // broken switch, so the empty slots are filled from the theme's current ramp:
  // the operator sees six swatches that match the grid and edits from there.
  const enable = (on: boolean) => {
    if (!cfg) return;
    if (!on) {
      save({ ...cfg, enabled: false });
      return;
    }
    const seeded = { ...cfg, enabled: true };
    for (const { key, cssVar } of MATRIX_VARS) {
      if (!String(seeded[key] ?? '').trim()) seeded[key] = effectiveMatrixColor(cssVar);
    }
    save(seeded);
  };

  // Reset clears the overrides but keeps the section switched on, then re-seeds
  // from the theme — "back to the theme's colours", which is what an operator
  // means by reset here, rather than "switch the whole feature off".
  const reset = () => {
    if (!cfg) return;
    applyMatrixColors({ ...emptyMatrixColors(), enabled: false });
    const seeded = { ...emptyMatrixColors(), enabled: true };
    for (const { key, cssVar } of MATRIX_VARS) seeded[key] = effectiveMatrixColor(cssVar);
    save(seeded);
  };

  if (!cfg) return null;

  return (
    <div className="space-y-3 border-t border-border/60 pt-4">
      <label className="flex items-start gap-2 text-sm cursor-pointer">
        <Checkbox checked={cfg.enabled} className="mt-0.5" onCheckedChange={(c) => enable(!!c)} />
        <span>
          {t('appr.matrixEnable')}{' '}
          <span className="text-xs text-muted-foreground">{t('appr.matrixHint')}</span>
        </span>
      </label>

      {cfg.enabled && (
        <div className="space-y-3">
          <div className="grid grid-cols-2 gap-x-4 gap-y-2">
            {MATRIX_VARS.map(({ key, cssVar, label }) => (
              <label key={key} className="flex items-center gap-2 text-sm cursor-pointer">
                <input
                  type="color"
                  value={String(cfg[key] || '').trim() || effectiveMatrixColor(cssVar)}
                  onChange={(e) => save({ ...cfg, [key]: e.target.value })}
                  className="size-6 rounded-md border border-border bg-transparent p-0 cursor-pointer shrink-0"
                />
                {t(label)}
              </label>
            ))}
          </div>

          {/* The matrix as it will actually look: same tokens, same shapes. */}
          <div className="flex items-center gap-1.5">
            <span className="text-xs text-muted-foreground w-10 shrink-0">{t('appr.matrixSample')}</span>
            <span className="inline-block w-7 h-5 rounded bg-mx-call-conf" />
            <span className="inline-block w-7 h-5 rounded bg-mx-call-work" />
            <span className="inline-block w-7 h-5 rounded bg-mx-dx-conf" />
            <span className="inline-block w-7 h-5 rounded bg-mx-dx-work" />
            <span className="inline-block w-7 h-5 rounded bg-mx-none" />
            <span className="inline-block w-7 h-5 rounded bg-mx-none ring-2 ring-mx-cur ring-inset" />
            <span className="relative inline-block w-7 h-5 rounded bg-mx-dx-conf">
              <span className="absolute top-[4px] right-[4px] size-[5px] rounded-full bg-mx-mark-work ring-1 ring-background" />
            </span>
            <span className="relative inline-block w-7 h-5 rounded bg-mx-dx-conf">
              <span className="absolute top-[4px] right-[4px] size-[5px] rounded-full bg-mx-mark-conf ring-1 ring-background" />
            </span>
          </div>

          <button type="button" onClick={reset}
            className="text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground">
            {t('appr.matrixReset')}
          </button>
        </div>
      )}
    </div>
  );
}

export function AppearancePanel() {
  const { t } = useI18n();
  const [cfg, setCfg] = useState<RowColorSettings | null>(null);

  useEffect(() => {
    (async () => {
      try { setCfg((await GetRowColors()) as any); } catch { /* defaults on the backend */ }
    })();
  }, []);

  const save = (next: RowColorSettings) => {
    setCfg(next);
    SaveRowColors(next as any).catch(() => {});
  };
  const patchRule = (id: string, patch: Partial<{ color: string; enabled: boolean; channels: string[] }>) => {
    if (!cfg) return;
    save({ ...cfg, rules: cfg.rules.map((r) => (r.id === id ? { ...r, ...patch } : r)) });
  };

  // The rule card shows the row exactly as the grid will draw it, so the choice
  // is made by looking rather than by imagining.
  const preview = (color: string): Record<string, string> => {
    const st = cfg?.style ?? 'bar';
    const pct = cfg?.intensity ?? 12;
    const out: Record<string, string> = {};
    if (st === 'tint' || st === 'both') out.backgroundColor = `color-mix(in srgb, ${color} ${pct}%, transparent)`;
    if (st === 'bar' || st === 'both') out.boxShadow = `inset 3px 0 0 ${color}`;
    return out;
  };

  if (!cfg) return <div className="p-1 text-sm text-muted-foreground">…</div>;

  return (
    <div className="space-y-4">
      <label className="flex items-start gap-2 text-sm cursor-pointer">
        <Checkbox checked={!!cfg.bandmap_lotw} className="mt-0.5"
          onCheckedChange={(c) => save({ ...cfg, bandmap_lotw: !!c } as any)} />
        <span>{t('appr.bandmapLotw')} <span className="text-xs text-muted-foreground">{t('appr.bandmapLotwHint')}</span></span>
      </label>

      {/* Banding is its own thing, above the QSL rules and outside them: it says
          nothing about a contact, it only helps the eye keep its line across a
          wide table. Turning the status colours off must not take it away. */}
      <div className="space-y-2">
        <label className="flex items-start gap-2 text-sm cursor-pointer">
          <Checkbox checked={(cfg as any).recent_zebra !== 'off'} className="mt-0.5"
            onCheckedChange={(c) => save({ ...cfg, recent_zebra: c ? '' : 'off' } as any)} />
          <span>{t('appr.zebra')} <span className="text-xs text-muted-foreground">{t('appr.zebraHint')}</span></span>
        </label>
        {(cfg as any).recent_zebra !== 'off' && (
          <div className="flex items-center gap-2 pl-6">
            <span className="text-xs text-muted-foreground">{t('appr.zebraColor')}</span>
            <input type="color" className="size-7 rounded border border-border bg-transparent p-0 cursor-pointer"
              value={/^#[0-9a-fA-F]{6}$/.test((cfg as any).recent_zebra_color ?? '') ? (cfg as any).recent_zebra_color : '#808080'}
              onChange={(e) => save({ ...cfg, recent_zebra_color: e.target.value } as any)} />
            {/* Empty means "follow the theme", and there has to be a way back to
                it — a colour chosen under one theme is wrong under the other. */}
            {(cfg as any).recent_zebra_color && (
              <button type="button" className="text-xs text-muted-foreground hover:text-foreground"
                onClick={() => save({ ...cfg, recent_zebra_color: '' } as any)}>{t('appr.zebraAuto')}</button>
            )}
          </div>
        )}
      </div>

      <label className="flex items-start gap-2 text-sm cursor-pointer">
        <Checkbox checked={cfg.enabled} className="mt-0.5"
          onCheckedChange={(c) => save({ ...cfg, enabled: !!c })} />
        <span>{t('appr.enable')} <span className="text-xs text-muted-foreground">{t('appr.enableHint')}</span></span>
      </label>

      {cfg.enabled && (
        <div className="space-y-3">
          {/* Style first: it decides whether the colours below are a signal or a
              wallpaper, which matters more than which hue they are. */}
          <div className="flex items-center gap-3 flex-wrap">
            <span className="text-sm">{t('appr.style')}</span>
            <div className="inline-flex rounded-md border border-border overflow-hidden text-xs">
              {(['bar', 'tint', 'both'] as const).map((v) => (
                <button key={v} type="button" onClick={() => save({ ...cfg, style: v })}
                  className={cn('px-3 py-1.5 font-medium', (cfg.style ?? 'bar') === v ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted')}>
                  {t('appr.style' + v[0].toUpperCase() + v.slice(1))}
                </button>
              ))}
            </div>
            {(cfg.style ?? 'bar') !== 'bar' && (
              <label className="flex items-center gap-2 text-sm">
                {t('appr.intensity')}
                <input type="range" min={5} max={45} step={1} value={cfg.intensity ?? 12}
                  onChange={(e) => save({ ...cfg, intensity: parseInt(e.target.value, 10) })}
                  className="w-32 accent-[var(--primary)]" />
                <span className="font-mono text-xs text-muted-foreground w-8">{cfg.intensity ?? 12}%</span>
              </label>
            )}
          </div>
          {/* Order matters and is shown: a contact is usually several of these at
              once, and the first match wins. */}
          <p className="text-xs text-muted-foreground">{t('appr.orderHint')}</p>
          {cfg.rules.map((r, i) => (
            <div key={r.id} className="rounded-lg border border-border/60 p-2.5 space-y-2"
              style={r.enabled ? preview(r.color) : undefined}>
              <label className="flex items-center gap-2 text-sm cursor-pointer">
                <Checkbox checked={r.enabled} onCheckedChange={(c) => patchRule(r.id, { enabled: !!c })} />
                <span className="font-mono text-xs text-muted-foreground">{i + 1}.</span>
                <span className="font-medium">{t(LABELS[r.id] ?? r.id)}</span>
              </label>
              {/* Which channels this category looks at. None ticked = all of
                  them, which is what an unnarrowed rule should mean. */}
              {r.enabled && r.id !== 'worked' && (
                <div className="flex items-center gap-3 flex-wrap pl-6 text-xs">
                  {CHANNELS.map((c) => {
                    const on = !r.channels?.length || r.channels.includes(c);
                    return (
                      <label key={c} className="flex items-center gap-1.5 cursor-pointer">
                        <Checkbox checked={on} onCheckedChange={(v) => {
                          const cur = r.channels?.length ? r.channels : [...CHANNELS];
                          const next = v ? [...new Set([...cur, c])] : cur.filter((x) => x !== c);
                          patchRule(r.id, { channels: next });
                        }} />
                        {CHANNEL_LABELS[c]?.startsWith('appr.') ? t(CHANNEL_LABELS[c]) : CHANNEL_LABELS[c]}
                      </label>
                    );
                  })}
                </div>
              )}
              {r.enabled && (
                <div className="flex items-center gap-1.5 flex-wrap pl-6">
                  {PALETTE.map((c) => (
                    <button key={c} type="button" title={c}
                      onClick={() => patchRule(r.id, { color: c })}
                      className={cn('size-6 rounded-md border-2 transition-transform hover:scale-110',
                        r.color.toLowerCase() === c ? 'border-foreground' : 'border-transparent')}
                      style={{ backgroundColor: c }} />
                  ))}
                  <input type="color" value={r.color} title={t('appr.custom')}
                    onChange={(e) => patchRule(r.id, { color: e.target.value })}
                    className="size-6 rounded-md border border-border bg-transparent p-0 cursor-pointer" />
                </div>
              )}
            </div>
          ))}
        </div>
      )}

      <MatrixColorsSection />
      <WidgetOrderSection />
    </div>
  );
}

// The row of widgets to the right of the entry, in the order they appear.
//
// Flexbox does the moving in the main view — this list only decides the order
// property each one gets. That is why a widget switched OFF still holds its
// place here: it comes back where the operator left it rather than at the end.
export const WIDGET_KEYS = [
  'livestations', 'chat', 'rotor', 'motorant', 'antgenius',
  'amp', 'tuner', 'scp', 'chasenew', 'watchlist', 'dvk', 'winkeyer', 'photo',
] as const;

const WIDGET_LABELS: Record<string, string> = {
  livestations: 'wo.livestations', chat: 'wo.chat', rotor: 'wo.rotor',
  motorant: 'wo.motorant', antgenius: 'wo.antgenius', amp: 'wo.amp',
  tuner: 'wo.tuner', scp: 'wo.scp', chasenew: 'wo.chasenew', watchlist: 'wo.watchlist',
  dvk: 'wo.dvk', winkeyer: 'wo.winkeyer', photo: 'wo.photo',
};

function readWidgetOrder(): string[] {
  try {
    const raw = localStorage.getItem('opslog.widgetOrder');
    const arr = raw ? JSON.parse(raw) : null;
    if (Array.isArray(arr)) {
      // A key from an older build that no longer exists is dropped; a widget
      // added since joins the end. An old preference can never hide a new one.
      const known = arr.filter((k: any) => (WIDGET_KEYS as readonly string[]).includes(k));
      return [...known, ...WIDGET_KEYS.filter((k) => !known.includes(k))];
    }
  } catch { /* corrupt pref → the default order */ }
  return [...WIDGET_KEYS];
}

function WidgetOrderSection() {
  const { t } = useI18n();
  const [order, setOrder] = useState<string[]>(readWidgetOrder);
  const dragKey = useRef<string | null>(null);
  const [dragging, setDragging] = useState<string | null>(null);
  // Where the row would land. Drawn as a line above the target rather than by
  // colouring it: the question a dragging hand asks is "between which two", and
  // a highlighted row answers a different one.
  const [over, setOver] = useState<string | null>(null);

  const commit = (keys: string[]) => {
    setOrder(keys);
    try { localStorage.setItem('opslog.widgetOrder', JSON.stringify(keys)); } catch { /* private mode */ }
    // The main view listens: an order is meant to be watched as it is dragged,
    // not discovered after closing Preferences.
    EventsEmit('widgets:order', keys);
  };
  const moveTo = (from: string, to: string) => {
    if (from === to) return;
    const next = order.filter((k) => k !== from);
    const at = next.indexOf(to);
    next.splice(at < 0 ? next.length : at, 0, from);
    commit(next);
  };

  return (
    <div className="space-y-2">
      <h3 className="text-sm font-semibold">{t('wo.title')}</h3>
      <p className="text-xs text-muted-foreground">{t('wo.hint')}</p>
      <div className="space-y-1 max-w-md">
        {/* The two that cannot move, shown so the order reads as the whole row
            rather than as a list that mysteriously starts at the third item. */}
        {['wo.entry', 'wo.details'].map((k) => (
          <div key={k}
            className="flex items-center gap-2 rounded-md border border-border/60 bg-muted/30 px-2 py-1.5 text-sm text-muted-foreground">
            <Lock className="size-3.5 shrink-0 opacity-60" />
            <span className="flex-1 min-w-0 truncate">{t(k)}</span>
          </div>
        ))}
        {order.map((k) => (
          // The WHOLE row is the handle, not the grip alone: a list whose rows
          // can only be moved by a 16-pixel icon is a list most people conclude
          // cannot be moved. The grip stays as the sign that it can.
          <div key={k} draggable
            onDragStart={(e) => { dragKey.current = k; setDragging(k); e.dataTransfer.effectAllowed = 'move'; }}
            onDragEnd={() => { dragKey.current = null; setDragging(null); setOver(null); }}
            onDragOver={(e) => {
              if (!dragKey.current) return;
              e.preventDefault();
              e.dataTransfer.dropEffect = 'move';
              if (over !== k) setOver(k);
            }}
            onDragLeave={() => { if (over === k) setOver(null); }}
            onDrop={(e) => {
              if (!dragKey.current) return;
              e.preventDefault();
              moveTo(dragKey.current, k);
              setOver(null);
            }}
            title={t('wo.drag')}
            className={cn('flex items-center gap-2 rounded-md border bg-card px-2 py-1.5 text-sm select-none',
              'cursor-grab active:cursor-grabbing transition-shadow',
              dragging === k ? 'opacity-50 border-primary shadow-lg' : 'border-border hover:border-foreground/30',
              // The landing line, on the edge the row would take.
              over === k && dragging !== k && 'shadow-[inset_0_3px_0_0_var(--primary)]')}>
            <GripVertical className="size-4 shrink-0 text-muted-foreground/50" />
            <span className="flex-1 min-w-0 truncate">{t(WIDGET_LABELS[k] ?? k)}</span>
          </div>
        ))}
      </div>
      <button type="button" onClick={() => commit([...WIDGET_KEYS])}
        className="text-xs text-muted-foreground hover:text-foreground underline">
        {t('wo.reset')}
      </button>
    </div>
  );
}
