import { load, save } from './storage';
import * as FontSize    from './features/font-size';
import * as Contrast    from './features/contrast';
import * as Dyslexia    from './features/dyslexia-font';
import * as Grayscale   from './features/grayscale';
import * as ReadGuide   from './features/reading-guide';
import * as Animations  from './features/animations';
import * as LineHeight  from './features/line-height';
import * as Cursor      from './features/cursor-enlarger';
import * as Links       from './features/highlight-links';
import * as ScreenMask  from './features/screen-mask';

export interface WidgetConfig {
  enabled?: boolean;
  color: string;
  position: string;
  features: string[];
  hide_powered_by?: boolean;
  button_size?: 'small' | 'medium' | 'large';
  offset_vertical?: number;
  offset_horizontal?: number;
}

type ToggleState = Record<string, boolean>;

const FEATURE_LABELS: Record<string, string> = {
  font:           'Font Size',
  contrast:       'High Contrast',
  dyslexia:       'Dyslexia Font',
  grayscale:      'Grayscale',
  'reading-guide':'Reading Guide',
  animations:     'Pause Animations',
  'line-height':  'Line Height',
  cursor:         'Large Cursor',
  links:          'Highlight Links',
  'screen-mask':  'Screen Mask',
};

const FEATURE_ICONS: Record<string, string> = {
  font:            'Aa',
  contrast:        '◑',
  dyslexia:        '\u{1D4BB}', // 𝒻 script f — indicates special font
  grayscale:       '◌',
  'reading-guide': '☰',
  animations:      '⏸',
  'line-height':   '↕',
  cursor:          '↖',
  links:           '⬡',
  'screen-mask':   '▤',
};

const CSS = `
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
/* Explicit cursor:auto reset — cursor is inherited, and the "Large Cursor"
   feature (cursor-enlarger.ts) sets a page-wide !important custom cursor on
   the host page. That inherited value would otherwise bleed into any element
   here that doesn't declare its own cursor (e.g. the panel header), making
   the oversized arrow appear to float over the widget's own controls. */
.w{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;line-height:1.4;color:#1e1e2e;cursor:auto}
.fab{width:var(--fab-size,52px);height:var(--fab-size,52px);border-radius:50%;border:2px solid rgba(255,255,255,.25);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:24px;color:#fff;box-shadow:0 2px 14px rgba(0,0,0,.28);transition:transform .15s}
.fab:hover{transform:scale(1.06)}
.fab:focus-visible{outline:3px solid #fff;outline-offset:4px;box-shadow:0 0 0 7px rgba(255,255,255,.28)}
.panel{position:absolute;width:292px;max-width:calc(100vw - 16px);display:flex;flex-direction:column;background:#fff;border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,.18);overflow:hidden;transition:opacity .15s,transform .15s,visibility 0s}
.panel.hidden{opacity:0;visibility:hidden;pointer-events:none;transform:translateY(8px);transition:opacity .15s,transform .15s,visibility 0s .15s}
.ph{display:flex;align-items:center;justify-content:space-between;padding:12px 14px 11px;border-bottom:1px solid #e5e7eb}
.pt{font-size:14px;font-weight:700}
.xb{background:none;border:none;cursor:pointer;font-size:17px;color:#6b7280;padding:4px 6px;border-radius:6px;line-height:1}
.xb:hover{background:#f3f4f6}
.xb:focus-visible{outline:2px solid var(--c);outline-offset:2px;background:#f3f4f6}
.fs{padding:6px 8px 8px;flex:1;min-height:0;overflow-y:auto}
.fr{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 8px;border-radius:8px;transition:background .12s,box-shadow .12s}
.fr:hover{background:#f0f4ff;box-shadow:inset 0 0 0 2px var(--c)}
.fr.fr-toggle{cursor:pointer}
.fr.fr-toggle:focus-visible{outline:2px solid var(--c);outline-offset:2px;background:#f0f4ff}
.fi{font-size:20px;width:28px;flex-shrink:0;display:flex;align-items:center;justify-content:center;line-height:1;opacity:.75}
.fl{font-size:13px;font-weight:500;flex:1}
.tb{width:44px;height:24px;border-radius:12px;border:none;cursor:pointer;position:relative;background:#d1d5db;flex-shrink:0;transition:background .18s}
.tb[aria-pressed=true]{background:var(--c)}
.tb::after{content:'';position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;transition:transform .18s;box-shadow:0 1px 3px rgba(0,0,0,.2)}
.tb[aria-pressed=true]::after{transform:translateX(20px)}
.tb:focus-visible{outline:2px solid var(--c);outline-offset:3px}
.sbs{display:flex;align-items:center;gap:4px}
.sb{padding:2px 9px;border:1px solid #d1d5db;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;font-weight:700;color:#374151;line-height:1.8}
.sb:hover{background:#f3f4f6}
.sb:focus-visible{outline:2px solid var(--c);outline-offset:2px}
.sv{font-size:11px;color:#6b7280;min-width:26px;text-align:center}
.pf{padding:8px 14px;border-top:1px solid #e5e7eb;text-align:center;font-size:10px;color:#9ca3af;letter-spacing:.02em}
.pf a{color:#9ca3af;text-decoration:none;font-weight:600}
.pf a:hover{color:var(--c)}
`;

function el<K extends keyof HTMLElementTagNameMap>(
  tag: K,
  attrs: Record<string, string> = {},
  cls = ''
): HTMLElementTagNameMap[K] {
  const e = document.createElement(tag);
  if (cls) e.className = cls;
  for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v);
  return e;
}

const FAB_SIZE_MAP: Record<string, string> = { small: '40px', medium: '52px', large: '64px' };

export function mountToolbar(shadow: ShadowRoot, config: WidgetConfig): void {
  const color   = config.color || '#7c6aff';
  const isTop   = config.position.startsWith('top');
  const isLeft  = config.position.endsWith('left');
  const fabSize = FAB_SIZE_MAP[config.button_size ?? 'medium'] ?? '52px';

  // Inject styles
  const style = document.createElement('style');
  style.textContent = CSS;
  shadow.appendChild(style);

  const wrapper = el('div', {}, 'w');
  wrapper.style.setProperty('--c', color);
  wrapper.style.setProperty('--fab-size', fabSize);

  // FAB
  const fab = el('button', {
    'aria-label':    'Accessibility options',
    'aria-expanded': 'false',
    'aria-haspopup': 'true',
  }, 'fab');
  fab.style.background = color;
  fab.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 375 466" fill="none" aria-hidden="true"><path d="M291.794 31.5377L296.452 44.4747C301.627 58.8331 312.934 70.1399 327.293 75.3147L340.23 79.9732C341.396 80.3967 341.396 82.0501 340.23 82.4678L327.293 87.1263C312.934 92.3011 301.627 103.608 296.452 117.966L291.794 130.903C291.37 132.069 289.717 132.069 289.299 130.903L284.641 117.966C279.466 103.608 268.159 92.3011 253.801 87.1263L240.864 82.4678C239.698 82.0443 239.698 80.3909 240.864 79.9732L253.801 75.3147C268.159 70.1399 279.466 58.8331 284.641 44.4747L289.299 31.5377C289.717 30.3658 291.37 30.3658 291.794 31.5377Z" fill="white"/><path d="M350.028 0.448155L352.389 6.99789C355.011 14.267 360.737 19.9929 368.006 22.6152L374.556 24.9763C375.148 25.191 375.148 26.0264 374.556 26.241L368.006 28.6022C360.737 31.2244 355.011 36.9503 352.389 44.2194L350.028 50.7692C349.813 51.3609 348.978 51.3609 348.763 50.7692L346.402 44.2194C343.78 36.9503 338.054 31.2244 330.785 28.6022L324.235 26.241C323.643 26.0264 323.643 25.191 324.235 24.9763L330.785 22.6152C338.054 19.9929 343.78 14.267 346.402 6.99789L348.763 0.448155C348.978 -0.149385 349.819 -0.149385 350.028 0.448155Z" fill="white"/><path d="M350.028 111.678L352.389 118.227C355.011 125.497 360.737 131.223 368.006 133.845L374.556 136.206C375.148 136.421 375.148 137.256 374.556 137.471L368.006 139.832C360.737 142.454 355.011 148.18 352.389 155.449L350.028 161.999C349.813 162.59 348.978 162.59 348.763 161.999L346.402 155.449C343.78 148.18 338.054 142.454 330.785 139.832L324.235 137.471C323.643 137.256 323.643 136.421 324.235 136.206L330.785 133.845C338.054 131.223 343.78 125.497 346.402 118.227L348.763 111.678C348.978 111.086 349.819 111.086 350.028 111.678Z" fill="white"/><path fill-rule="evenodd" clip-rule="evenodd" d="M158.789 147.819C190.109 147.819 215.5 122.439 215.5 91.1311C215.5 59.8225 190.109 34.442 158.789 34.442C127.469 34.442 102.079 59.8225 102.079 91.1302C102.079 122.439 127.469 147.819 158.789 147.819ZM209.015 451.011L169.115 363.267C165.085 354.406 152.494 354.406 148.464 363.267L108.565 451.011C104.615 459.698 95.95 465.275 86.404 465.275C71.042 465.275 59.5211 451.227 62.5339 436.17L97.2076 321.245C100.109 306.741 111.503 253.945 80.3666 226.463L12.1909 188.603C4.66609 184.424 0 176.496 0 167.892C0 152.151 15.0724 140.786 30.213 145.111L130.635 162.116C149.036 167.371 168.543 167.371 186.943 162.116L287.366 145.111C302.507 140.785 317.579 152.15 317.579 167.892C317.579 176.496 312.912 184.424 305.388 188.603L237.212 226.463C206.076 248.107 217.469 306.741 220.371 321.245L255.045 436.17C258.058 451.227 246.537 465.275 231.175 465.275C221.629 465.275 212.964 459.698 209.015 451.011Z" fill="white"/></svg>';

  // Panel — offset by fab size + 8px gap so it clears the FAB
  const fabPx    = parseInt(fabSize, 10);
  const panelOff = `${fabPx + 8}px`;
  const panel = el('div', { role: 'dialog', 'aria-label': 'Accessibility toolbar' }, 'panel hidden');
  panel.style.cssText = [
    isTop  ? `top:${panelOff}`    : `bottom:${panelOff}`,
    isLeft ? 'left:0'      : 'right:0',
    // Caps the panel at whatever vertical space is left between the FAB
    // offset and the opposite screen edge — without this, a short/narrow
    // viewport lets the panel grow taller than the screen with no way to
    // scroll the overflow back into view (the .fs list's own overflow-y
    // only scrolls within its box, it can't reposition the box itself).
    `max-height:calc(100vh - ${panelOff} - 16px)`,
  ].join(';');

  // Panel header
  const header = el('div', {}, 'ph');
  const title  = el('span', {}, 'pt');
  title.textContent = 'Accessibility';
  title.style.color = color;
  const closeBtn = el('button', { 'aria-label': 'Close accessibility panel' }, 'xb');
  closeBtn.textContent = '✕';
  header.appendChild(title);
  header.appendChild(closeBtn);
  panel.appendChild(header);

  // Feature list
  const featureList = el('div', {}, 'fs');
  panel.appendChild(featureList);

  // Footer
  if (!config.hide_powered_by) {
    const footer = el('div', {}, 'pf');
    footer.innerHTML = 'Powered by <a href="https://accessmate.ai/" target="_blank" rel="noopener noreferrer">AccessMate</a>';
    panel.appendChild(footer);
  }

  wrapper.appendChild(fab);
  wrapper.appendChild(panel);
  shadow.appendChild(wrapper);

  // ── State ──────────────────────────────────────────────────
  const prefs      = load();
  const state: ToggleState = {};
  const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  // Large Cursor replaces the mouse pointer via CSS `cursor:` — a no-op on a
  // touch-only device (no pointer to replace), so skip it there rather than
  // show a toggle that visibly does nothing.
  const noFinePointer = window.matchMedia('(hover: none) and (pointer: coarse)').matches;

  // ── Build feature controls ─────────────────────────────────
  // Render in FEATURE_LABELS' fixed declaration order (filtered to enabled
  // features), not config.features' stored order — that array's order drifts
  // over time as features are toggled off/on in the admin UI (re-enabling one
  // appends it to the end), which would otherwise scramble the on-site order
  // out of sync with the Widget Configurator's preview, which already uses
  // this same fixed-order approach.
  for (const id of Object.keys(FEATURE_LABELS)) {
    if (!config.features.includes(id)) continue;
    if (id === 'cursor' && noFinePointer) continue;
    const label = FEATURE_LABELS[id];

    const row = el('div', {}, 'fr');
    const ico = el('span', { 'aria-hidden': 'true' }, 'fi');
    ico.textContent = FEATURE_ICONS[id] ?? '●';
    const lbl = el('span', {}, 'fl');
    lbl.textContent = label;
    row.appendChild(ico);
    row.appendChild(lbl);

    if (id === 'font') {
      const saved = Number(prefs['font-step'] ?? 0);
      FontSize.setStep(saved);

      const btns  = el('div', {}, 'sbs');
      const decBtn = el('button', { 'aria-label': 'Decrease font size' }, 'sb');
      decBtn.textContent = 'A−';
      const incBtn = el('button', { 'aria-label': 'Increase font size' }, 'sb');
      incBtn.textContent = 'A+';
      const stepVal = el('span', {}, 'sv');
      stepVal.textContent = saved !== 0 ? `${saved > 0 ? '+' : ''}${saved}` : '—';

      decBtn.addEventListener('click', () => {
        FontSize.decrease();
        const s = FontSize.getStep();
        stepVal.textContent = s !== 0 ? `${s > 0 ? '+' : ''}${s}` : '—';
        updatePrefs('font-step', s);
      });
      incBtn.addEventListener('click', () => {
        FontSize.increase();
        const s = FontSize.getStep();
        stepVal.textContent = s !== 0 ? `${s > 0 ? '+' : ''}${s}` : '—';
        updatePrefs('font-step', s);
      });

      btns.appendChild(decBtn);
      btns.appendChild(stepVal);
      btns.appendChild(incBtn);
      row.appendChild(btns);

    } else if (id === 'line-height') {
      const savedIdx = Number(prefs['line-height-step'] ?? 0);
      LineHeight.setStep(savedIdx);
      const LH_LABELS = ['Off', '1.5×', '1.8×', '2.0×'];

      const cycleBtn = el('button', { 'aria-label': 'Cycle line height' }, 'sb');
      cycleBtn.style.minWidth = '44px';
      cycleBtn.textContent = LH_LABELS[savedIdx] ?? 'Off';

      cycleBtn.addEventListener('click', () => {
        const next = LineHeight.cycle();
        const idx  = LineHeight.getStep();
        cycleBtn.textContent = next === 0 ? 'Off' : LH_LABELS[idx] ?? 'Off';
        updatePrefs('line-height-step', idx);
      });

      row.appendChild(cycleBtn);

    } else {
      // Standard toggle
      const savedOn = Boolean(prefs[id]);
      // Respect prefers-reduced-motion for reading-guide
      const startOn = id === 'reading-guide' && reducedMotion ? false : savedOn;
      state[id] = startOn;
      applyFeature(id, startOn);

      // Give the label element a unique id so the toggle button can reference it
      const lblId = `acm-lbl-${id}`;
      lbl.id = lblId;

      const btn = el('button', {
        'aria-pressed':    startOn ? 'true' : 'false',
        'aria-labelledby': lblId,
      }, 'tb');

      btn.addEventListener('click', () => {
        state[id] = !state[id];
        const isOn = state[id];
        btn.setAttribute('aria-pressed', isOn ? 'true' : 'false');
        applyFeature(id, isOn);
        updatePrefs(id, isOn);
      });

      row.appendChild(btn);

      // Make the entire row clickable — clicking the icon or label triggers
      // the toggle button, but we skip the event when it already originates
      // from the button itself to avoid double-firing.
      row.classList.add('fr-toggle');
      row.setAttribute('role', 'group');
      row.setAttribute('aria-label', label);
      row.addEventListener('click', (e: MouseEvent) => {
        if (e.target === btn || btn.contains(e.target as Node)) return;
        btn.click();
      });

      // Also allow Space / Enter on the row itself for keyboard users who
      // tab to it before reaching the inner button.
      row.setAttribute('tabindex', '-1'); // not in tab order itself; btn already is
    }

    featureList.appendChild(row);
  }

  // ── Panel toggle ────────────────────────────────────────────
  const openPanel = () => {
    panel.classList.remove('hidden');
    fab.setAttribute('aria-expanded', 'true');
    closeBtn.focus();
  };
  const closePanel = () => {
    panel.classList.add('hidden');
    fab.setAttribute('aria-expanded', 'false');
    fab.focus();
  };

  fab.addEventListener('click', () => {
    panel.classList.contains('hidden') ? openPanel() : closePanel();
  });
  closeBtn.addEventListener('click', closePanel);

  // Close on outside click (clicks outside the shadow host)
  document.addEventListener('click', (e: MouseEvent) => {
    if (panel.classList.contains('hidden')) return;
    if (!shadow.host.contains(e.target as Node)) closePanel();
  });

  // Close on Escape key
  panel.addEventListener('keydown', (e: KeyboardEvent) => {
    if (e.key === 'Escape') closePanel();
  });
}

// ── Helpers ──────────────────────────────────────────────────

function applyFeature(id: string, on: boolean): void {
  switch (id) {
    case 'contrast':       Contrast.toggle(on);   break;
    case 'dyslexia':       Dyslexia.toggle(on);   break;
    case 'grayscale':      Grayscale.toggle(on);  break;
    case 'reading-guide':  ReadGuide.toggle(on);  break;
    case 'animations':     Animations.toggle(on); break;
    case 'cursor':         Cursor.toggle(on);     break;
    case 'links':          Links.toggle(on);      break;
    case 'screen-mask':    ScreenMask.toggle(on); break;
  }
}

function updatePrefs(key: string, val: unknown): void {
  const prefs = load();
  prefs[key]  = val;
  save(prefs);
}
