import { h, clear } from '../core/dom';
import { store } from '../core/store';
import { icon } from '../ui/icons';
import { colorPicker } from '../ui/color';
import { segmented, slider, select, numberInput, textInput, field, checkbox } from '../ui/controls';
import { markThickness, type Anno, type TextAnno } from '../core/model';
import { FONT_FAMILIES } from '../core/fonts';
import { ensureFamilyLoaded } from '../core/font-loader';

const INK_COLORS = ['#12141b', '#565d6b', '#ffffff', '#2f6bff', '#7b5cff', '#12b5c9', '#1aa053', '#e0a500', '#e0483d', '#ff8fab'];
const HL_COLORS = ['#ffe27a', '#a6f0c6', '#bcd4ff', '#ffc4dd', '#d9c6ff', '#ffd4a6'];
const BG_COLORS = ['#ffffff', '#eef1f7', '#dbe7ff', '#e7f9ef', '#fff3d6', '#ffe1e1'];

export class Properties {
  /** Set while the panel edits its own selection, so we don't rebuild the
   *  control the user is actively typing into (which would drop focus). */
  private suppress = false;
  private lastCp = 0;

  constructor(private el: HTMLElement) {
    store.on('selection', () => { if (!store.silent) this.render(); });
    store.on('tool', () => { if (!store.silent) this.render(); });
    store.on('annos', () => { if (!this.suppress && !store.silent) this.render(); });
    store.on('doc', () => { if (!store.silent) this.render(); });
    store.on('settings', () => { if (!store.silent) this.render(); });
    store.refreshPanel = () => this.render();
  }

  private lastKey = '';

  render() {
    // preserve the panel's scroll position while editing the same object
    // (e.g. clicking Rotate/Flip repeatedly), but reset it when the selection
    // changes. Keyed on selection only, so the auto text→cursor tool switch
    // after typing doesn't reset the scroll.
    const key = store.selection.join(',') || '|' + store.tool;
    const prevScroll = this.el.scrollTop;
    clear(this.el);
    const sel = store.selectedAnnos;
    if (sel.length === 1) this.renderForAnno(sel[0]);
    else if (sel.length > 1) this.renderMulti(sel);
    else this.renderToolDefaults();
    this.el.scrollTop = key === this.lastKey ? prevScroll : 0;
    this.lastKey = key;
  }

  private header(title: string) { return h('div.prop-title', title); }

  private group(label: string, ...controls: (Node | null)[]) {
    return h('div.prop-group', h('div.prop-label', label), ...controls.filter(Boolean) as Node[]);
  }

  /** The persistent "switch to cursor after adding" toggle. */
  private autoCursorToggle() {
    return h('div.prop-setting',
      checkbox('Switch to cursor after adding', store.autoCursor, (v) => store.setAutoCursor(v)),
      h('div.hint', 'Return to the Select tool automatically after placing an item, so you can move it right away.'),
    );
  }

  /** The persistent "lock aspect ratio when resizing" toggle. */
  private uniformToggle() {
    return h('div.prop-block',
      checkbox('Uniform scaling (keep aspect ratio)', store.uniformScale, (v) => store.setUniformScale(v)),
      h('div.hint', 'Lock proportions when resizing. Hold Shift while dragging to invert.'),
    );
  }

  private renderToolDefaults() {
    const t = store.tool;
    const ts = store.toolStyle;
    if (t === 'select') {
      this.el.append(
        h('div.prop-empty', icon('cursor', 26), h('p', 'Select something on the page to edit it — or pick a tool to start adding.')),
        this.uniformToggle(),
        this.autoCursorToggle(),
      );
      return;
    }
    const parts: Node[] = [this.header(labelForTool(t))];
    if (t === 'text') {
      parts.push(
        this.group('Font', select({ options: fontOpts(), value: ts.fontFamily, onchange: (v) => { ts.fontFamily = v; ensureFamilyLoaded(v); } })),
        h('div.prop-row',
          this.group('Size', numberInput({ value: ts.fontSize, min: 6, max: 200, onchange: (v) => (ts.fontSize = v || 16) })),
          this.styleToggles(!!ts.bold, !!ts.italic, !!ts.underline, (k) => { ts[k] = !ts[k]; this.render(); }),
        ),
        this.group('Color', colorPicker({ presets: INK_COLORS, value: ts.color, onpick: (c) => (ts.color = c) })),
      );
    } else if (t === 'highlight') {
      parts.push(this.group('Color', colorPicker({ presets: HL_COLORS, value: ts.highlight, onpick: (c) => (ts.highlight = c) })));
    } else if (['draw', 'rect', 'ellipse', 'line', 'arrow', 'underline', 'strike'].includes(t)) {
      parts.push(
        this.group('Color', colorPicker({ presets: INK_COLORS, value: ts.color, onpick: (c) => (ts.color = c) })),
        this.group('Stroke width', slider({ value: ts.strokeWidth, min: 1, max: 20, onchange: (v) => (ts.strokeWidth = v) })),
      );
    } else if (t === 'note') {
      parts.push(this.group('Color', colorPicker({ presets: HL_COLORS, value: ts.highlight, onpick: (c) => (ts.highlight = c) })));
    } else if (t === 'signature') {
      parts.push(...this.signatureControls(), h('p.hint', store.reuseSignature ? 'Click the page to drop your last signature.' : 'Click the page to open the signature picker.'));
    } else if (t.startsWith('field-')) {
      parts.push(h('p.hint', 'Drag on the page to place the field, then style it here once it’s selected.'));
    } else {
      parts.push(h('p.hint', 'Drag on the page to place it.'));
    }
    parts.push(h('div.prop-tip', 'These are defaults for new items. Draw on the page to add one.'));
    parts.push(this.uniformToggle());
    parts.push(this.autoCursorToggle());
    this.el.append(...parts);
  }

  private renderMulti(sel: Anno[]) {
    this.el.append(
      this.header(`${sel.length} items selected`),
      this.group('Opacity', slider({ value: Math.round((sel[0].opacity ?? 1) * 100), min: 10, max: 100, onchange: (v) => this.patch({ opacity: v / 100 }) })),
      this.uniformToggle(),
      this.actions(),
    );
  }

  private renderForAnno(a: Anno) {
    const parts: Node[] = [this.header(labelForType(a))];
    if (a.type === 'text') {
      const contentTa = h('textarea.input prop-textarea', {
        rows: 3, value: a.text, placeholder: 'Type the text…',
        oninput: () => this.patch({ text: contentTa.value }),
        // re-sync from the model on focus in case it changed (e.g. an on-canvas
        // edit was committed) while this control wasn't rebuilt.
        onfocus: () => { const cur = store.doc?.annos.find((x) => x.id === a.id) as any; if (cur && cur.text !== contentTa.value) contentTa.value = cur.text; },
      }) as HTMLTextAreaElement;
      parts.push(
        this.group('Text', contentTa),
        this.group('Font', select({ options: fontOpts(), value: a.fontFamily, onchange: (v) => { ensureFamilyLoaded(v); this.patch({ fontFamily: v }); } })),
        h('div.prop-row',
          this.group('Size', numberInput({ value: a.fontSize, min: 6, max: 200, onchange: (v) => this.patch({ fontSize: v || a.fontSize }) })),
          this.styleToggles(!!a.bold, !!a.italic, !!a.underline, (k) => { const p: Partial<TextAnno> = {}; p[k] = !a[k]; this.patchStructural(p); }),
        ),
        this.group('Color', colorPicker({ presets: INK_COLORS, value: a.color, onpick: (c) => this.patch({ color: c }) })),
        this.group('Align', segmented({
          options: [{ value: 'left', label: alignIcon('left') }, { value: 'center', label: alignIcon('center') }, { value: 'right', label: alignIcon('right') }],
          value: a.align, onchange: (v) => this.patch({ align: v as any }),
        })),
        checkbox('White background', !!a.bg, (v) => this.patch({ bg: v ? '#ffffff' : null })),
      );
    } else if (a.type === 'highlight') {
      parts.push(this.group('Color', colorPicker({ presets: HL_COLORS, value: a.color, onpick: (c) => this.patch({ color: c }) })));
    } else if (a.type === 'underline' || a.type === 'strike') {
      parts.push(
        this.group('Color', colorPicker({ presets: INK_COLORS, value: a.color, onpick: (c) => this.patch({ color: c }) })),
        this.group('Stroke width', slider({ value: Math.round(markThickness(a)), min: 1, max: 20, onchange: (v) => this.patch({ strokeWidth: v }) })),
      );
    } else if (a.type === 'draw') {
      parts.push(
        this.group('Color', colorPicker({ presets: INK_COLORS, value: a.color, onpick: (c) => this.patch({ color: c }) })),
        this.group('Stroke width', slider({ value: a.strokeWidth, min: 1, max: 20, onchange: (v) => this.patch({ strokeWidth: v }) })),
      );
    } else if (a.type === 'shape') {
      parts.push(this.group('Stroke', colorPicker({ presets: INK_COLORS, value: a.stroke, onpick: (c) => this.patch({ stroke: c }) })));
      parts.push(this.group('Stroke width', slider({ value: a.strokeWidth, min: 1, max: 20, onchange: (v) => this.patch({ strokeWidth: v }) })));
      if (a.shape === 'rect' || a.shape === 'ellipse') {
        parts.push(this.group('Fill', colorPicker({ presets: [...INK_COLORS], value: a.fill ?? 'none', allowNone: true, onpick: (c) => this.patch({ fill: c === 'none' ? null : c }) })));
      }
    } else if (a.type === 'note') {
      parts.push(
        this.group('Color', colorPicker({ presets: HL_COLORS, value: a.color, onpick: (c) => this.patch({ color: c }) })),
        h('button.btn btn-ghost btn-sm', { onclick: () => document.querySelector<HTMLElement>(`.anno[data-id="${a.id}"]`)?.dispatchEvent(new MouseEvent('dblclick', { bubbles: true })) }, 'Edit note text'),
      );
    } else if (a.type === 'link') {
      parts.push(this.group('Link URL', textInput({ value: a.url, oninput: (v) => this.patch({ url: v }) })));
    } else if (a.type === 'signature') {
      parts.push(...this.signatureControls());
    } else if (a.type === 'field') {
      parts.push(this.group('Field name', textInput({ value: a.name, oninput: (v) => this.patch({ name: v }) })));
      if (a.field === 'dropdown' || a.field === 'radio') {
        const ta = h('textarea.input', { rows: 3, value: (a.options || []).join('\n'), oninput: () => this.patch({ options: ta.value.split('\n').map((s) => s.trim()).filter(Boolean) }) }) as HTMLTextAreaElement;
        parts.push(this.group('Options (one per line)', ta));
      }
      if (a.field === 'text') parts.push(this.group('Default value', textInput({ value: (a.value as string) || '', autofill: true, oninput: (v) => this.patch({ value: v }) })));
      if (a.field === 'checkbox') parts.push(checkbox('Checked by default', a.value === true, (v) => this.patch({ value: v })));
      parts.push(checkbox('Required', !!a.required, (v) => this.patch({ required: v })));

      // ---- appearance ----
      parts.push(h('div.prop-divider'));
      parts.push(this.group('Style', segmented({
        options: [{ value: 'box', label: 'Box' }, { value: 'filled', label: 'Filled' }, { value: 'underline', label: 'Underline' }, { value: 'minimal', label: 'Minimal' }],
        value: a.fieldStyle || 'box', onchange: (v) => this.patchStructural({ fieldStyle: v as any }),
      })));
      if (a.field !== 'checkbox') {
        parts.push(this.group('Text color', colorPicker({ presets: INK_COLORS, value: a.textColor || '#12141b', onpick: (c) => this.patch({ textColor: c }) })));
      }
      const style = a.fieldStyle || 'box';
      if (style === 'box' || style === 'filled') {
        parts.push(this.group('Background', colorPicker({ presets: BG_COLORS, value: a.bgColor ?? 'none', allowNone: true, onpick: (c) => this.patch({ bgColor: c === 'none' ? null : c }) })));
      }
      if (style === 'box' || style === 'underline' || style === 'minimal') {
        parts.push(this.group('Border color', colorPicker({ presets: INK_COLORS, value: a.borderColor || '#2f6bff', onpick: (c) => this.patch({ borderColor: c }) })));
        parts.push(this.group('Border width', slider({ value: a.borderWidth ?? 1, min: 0.5, max: 6, step: 0.5, onchange: (v) => this.patch({ borderWidth: v }) })));
      }
      if (a.field === 'text' || a.field === 'dropdown' || a.field === 'radio') {
        parts.push(this.group('Font size', numberInput({ value: a.fontSize ?? 12, min: 6, max: 48, onchange: (v) => this.patch({ fontSize: v || 12 }) })));
      }
    }
    const isLineShape = a.type === 'shape' && (a.shape === 'line' || a.shape === 'arrow');
    const canTransform = a.type !== 'note' && a.type !== 'link' && a.type !== 'field' && !isLineShape;
    if (canTransform) parts.push(this.transformControls(a));
    if (a.type === 'image' || a.type === 'signature' || a.type === 'shape' || a.type === 'draw' || a.type === 'text') {
      parts.push(this.group('Opacity', slider({ value: Math.round((a.opacity ?? 1) * 100), min: 10, max: 100, onchange: (v) => this.patch({ opacity: v / 100 }) })));
    }
    if (a.type !== 'note') parts.push(this.uniformToggle());
    parts.push(this.actions());
    this.el.append(...parts);
  }

  /** Rotate / flip controls, shown for any transformable object. */
  private transformControls(a: Anno): Node {
    const rot = Math.round(a.rotation || 0);
    const flipH = !!(a as any).flipH;
    const flipV = !!(a as any).flipV;
    const rotInput = numberInput({ value: rot, min: -360, max: 360, onchange: (v) => this.patch({ rotation: norm360(v) }) });
    return h('div.prop-group',
      h('div.prop-label', 'Transform'),
      h('div.transform-row',
        h('button.tf-btn', { title: 'Rotate left 90°', onclick: () => this.patchStructural({ rotation: norm360(rot - 90) }) }, h('span.tf-mirror', icon('rotate', 15))),
        h('button.tf-btn', { title: 'Rotate right 90°', onclick: () => this.patchStructural({ rotation: norm360(rot + 90) }) }, icon('rotate', 15)),
        rotInput,
        h('span.tf-deg', '°'),
      ),
      h('div.transform-row',
        h('button.tf-btn tf-wide' + (flipH ? ' on' : ''), { title: 'Flip horizontal', onclick: () => this.patchStructural({ flipH: !flipH }) }, 'Flip H'),
        h('button.tf-btn tf-wide' + (flipV ? ' on' : ''), { title: 'Flip vertical', onclick: () => this.patchStructural({ flipV: !flipV }) }, 'Flip V'),
        h('button.tf-btn', { title: 'Reset transform', onclick: () => this.patchStructural({ rotation: 0, flipH: false, flipV: false }) }, icon('undo', 15)),
      ),
    );
  }

  /** Independent Bold / Italic / Underline toggles for text (replaces the old
   *  single-select control, which couldn't represent bold+italic together). */
  private styleToggles(bold: boolean, italic: boolean, underline: boolean, onToggle: (key: 'bold' | 'italic' | 'underline') => void): Node {
    const btn = (on: boolean, label: string, key: 'bold' | 'italic' | 'underline', cls: string) =>
      h('button.tf-btn ' + cls + (on ? ' on' : ''), { title: key[0].toUpperCase() + key.slice(1), onclick: () => onToggle(key) }, label);
    return h('div.prop-group',
      h('div.prop-label', 'Style'),
      h('div.transform-row',
        btn(bold, 'B', 'bold', 'style-b'),
        btn(italic, 'I', 'italic', 'style-i'),
        btn(underline, 'U', 'underline', 'style-u'),
      ),
    );
  }

  /** Signature reuse toggle + "add or edit signatures" button. */
  private signatureControls(): Node[] {
    return [
      h('div.prop-block',
        checkbox('Reuse last signature automatically', store.reuseSignature, (v) => store.setReuseSignature(v)),
        h('div.hint', 'Place your most recent signature with one click — no picker.'),
      ),
      h('button.btn btn-ghost btn-sm prop-full-btn', { onclick: () => store.openSignatureManager?.() }, icon('signature', 15), 'Add or edit signatures'),
    ];
  }

  private actions() {
    return h('div.prop-actions',
      h('button.btn btn-soft btn-sm', { onclick: () => this.duplicate() }, icon('duplicate', 15), 'Duplicate'),
      h('button.btn btn-danger btn-sm', { onclick: () => { store.checkpoint(); store.removeAnnos(store.selection); } }, icon('trash', 15), 'Delete'),
    );
  }
  private duplicate() {
    store.checkpoint();
    const clones = store.selectedAnnos.map((a) => ({ ...structuredClone(a), id: crypto.randomUUID?.() ?? Math.random().toString(36), x: a.x + 12, y: a.y + 12 }));
    for (const c of clones) store.addAnno(c as Anno);
    store.select(clones.map((c) => c.id));
  }

  /** Live edit from a panel control: coalesce undo checkpoints and DON'T rebuild
   *  the panel (keeps text inputs / sliders focused while editing). */
  private patch(p: Partial<Anno>) {
    const now = Date.now();
    if (now - this.lastCp > 600) store.checkpoint();
    this.lastCp = now;
    this.suppress = true;
    store.updateSelected(p);
    this.suppress = false;
  }

  /** A change that adds/removes other controls (e.g. field style) — rebuild the panel. */
  private patchStructural(p: Partial<Anno>) {
    store.checkpoint();
    this.lastCp = Date.now();
    store.updateSelected(p);
  }
}

function norm360(v: number): number {
  return (((Math.round(v) % 360) + 360) % 360);
}
function fontOpts() {
  return FONT_FAMILIES.map((f) => ({ value: f.id, label: f.label }));
}
function labelForType(a: Anno): string {
  const map: Record<string, string> = { text: 'Text', draw: 'Drawing', shape: 'Shape', highlight: 'Highlight', underline: 'Underline', strike: 'Strikethrough', note: 'Sticky note', image: 'Image', signature: 'Signature', link: 'Link', field: 'Form field' };
  return map[a.type] ?? 'Item';
}
const SHAPE_TOOLS = ['rect', 'ellipse', 'line', 'arrow'];
function labelForTool(t: string): string {
  const type = t.startsWith('field') ? 'field' : SHAPE_TOOLS.includes(t) ? 'shape' : t;
  return labelForType({ type } as any) || t;
}
function alignIcon(kind: 'left' | 'center' | 'right') {
  const a = kind === 'left' ? 'flex-start' : kind === 'center' ? 'center' : 'flex-end';
  return h('div', { style: { display: 'flex', flexDirection: 'column', alignItems: a, gap: '2px', width: '16px' } },
    h('span', { style: { height: '2px', width: '80%', background: 'currentColor', borderRadius: '2px' } }),
    h('span', { style: { height: '2px', width: '55%', background: 'currentColor', borderRadius: '2px' } }));
}
