import { useEffect, useRef, useState } from 'react';
import { ShirtOptionsPanel } from './ShirtOptionsPanel';
import { UploadTab } from './UploadTab';
import { StickersTab } from './StickersTab';
import { EmojiTab } from './EmojiTab';
import { TextTab } from './TextTab';
import { LayersPanel } from '../panels/LayersPanel';
import { formatUsd } from '../../constants/shirt';
import '../../styles/Sidebar.css';
// Tab nav for the right rail. Templates are deliberately absent — templates
// are loaded exclusively via the `?template=X` URL parameter (see
// `readTemplateFromUrl` in App.jsx). The Layers panel used to be a tab here
// too; it's now a permanent section rendered beneath the tab card so the
// user can browse / reorder / delete elements without losing access to the
// upload / stickers / text tools.
const TABS = [
  { id: 'upload', label: 'Upload Photo', icon: UploadIcon },
  { id: 'stickers', label: 'Stickers', icon: StickersIcon },
  { id: 'emoji', label: 'Emoji', icon: EmojiIcon },
  { id: 'text', label: 'Text', icon: TextIcon },
];

/**
 * Right-rail panel.
 *
 * Top: Shirt Options card (color/size/price)
 * Middle: Tab nav + active tab content
 * Bottom (sticky): Preview-on-Model toggle + Add to Cart button
 */
export function Sidebar({
  // Editor wiring
  onAddImage, onAddSticker, onAddText,
  recentUploads,
  onRemoveUpload,
  // Selection-aware editing surface for the text tab. NOTE:
  // `selectedElement` is used by the layers panel and the mobile
  // bottom-sheet compact mode. The Text tab now reads from a
  // SEPARATE prop (`editingTextElement`) so that *selecting* a text
  // element on the canvas no longer pulls the Text tab into edit
  // mode — only an explicit edit gesture (pencil affordance on
  // desktop, Edit-text toolbar button on mobile) sets that prop.
  // See the Change 7 follow-up in [[Refinements_2026-05-20_Part2]].
  selectedElement, editingTextElement, onUpdateSelected, onCommit,
  // Shirt config
  shirtColorId, onShirtColorChange,
  shirtSize, onShirtSizeChange,
  totalPrice, basePrice,
  // Cart
  onAddToCart,
  cartDisabled = false,
  cartDisabledReason = null,
  // Color helpers for the text tab
  recentColors,
  onPickColor,
  // Font helpers for the text tab (recent fonts dropdown)
  recentFonts,
  onPickFont,
  // Layers tab — needs the full elements list and a delete callback. We
  // already have selectedElement; add elements + onSelectElement +
  // onDeleteElement so the LayersPanel can show the list and act on it.
  elements = [],
  onSelectElement,
  onDeleteElement,
  // Layers-panel per-row actions (Change 4). Optional so existing
  // callers continue to work — the buttons just don't render when
  // the callback is missing.
  onDuplicateElement,
  onUpdateElement,
  // S3 multi-select wiring. LayersPanel uses these to render the
  // ✓ chip per row, the bulk-delete affordance, and the drag-reorder
  // grip. All optional — the panel falls back to single-selection
  // rendering when they aren't provided.
  selectedIds,
  onToggleInSelection,
  onDeleteMany,
  onReorderElement,
  // Controlled active-tab API (Change 7 follow-up). Sidebar used to
  // own its tab state internally; lifting it to App lets the pencil
  // affordance / Edit-text gesture set the tab directly (“clicking
  // the pencil should switch to the Text tab and populate the
  // form”). Both props are optional so existing call sites that
  // didn't pass them fall back to internal state — uncontrolled.
  activeTab: controlledActiveTab,
  onActiveTabChange,
  // Desktop focus signal for the Text tab. Threaded straight through
  // to TextTab — see its docblock for how it's consumed. Optional;
  // when omitted, TextTab simply never auto-focuses (which is fine
  // for any caller that doesn't need the pencil-click behaviour).
  textEditFocusToken,
}) {
  // Controlled or uncontrolled. When the parent supplies `activeTab`
  // and `onActiveTabChange`, those drive the visible tab; otherwise
  // we keep our own `useState` and behave as before. Detecting via
  // `!== undefined` so a deliberate `null` from the parent (= reset
  // to default) is still respected.
  const [internalActiveTab, setInternalActiveTab] = useState('upload');
  const isControlled = controlledActiveTab !== undefined && typeof onActiveTabChange === 'function';
  const activeTab = isControlled ? controlledActiveTab : internalActiveTab;
  const setActiveTab = isControlled ? onActiveTabChange : setInternalActiveTab;

  // ---------------------------------------------------------------
  // Pencil-click focus plumbing (Change 7 follow-up).
  //
  // The textarea-focus logic LIVES HERE — in Sidebar — rather than
  // inside TextTab, on purpose. TextTab is mounted/unmounted as the
  // user navigates between tabs; pressing the pencil flips the tab
  // to 'text' AND bumps the focus token in the same React batch,
  // which means TextTab is mounting FRESH at the moment the token
  // first changes. Any `useRef(focusToken)` initializer inside
  // TextTab would capture the new value at mount and the
  // "transition detection" effect would immediately bail out
  // (`1 === 1`) — the user has to click the pencil a second time
  // before TextTab's ref carries an old value.
  //
  // Sidebar, by contrast, is persistently mounted on desktop
  // (App.jsx wraps it in an <aside> that lives for the lifetime of
  // the editor). Its refs survive every tab switch. The ref
  // initializer captures the very-first token value (0) at app
  // boot, before any pencil click can have happened — so the next
  // bump is correctly detected as a transition.
  //
  // textareaRef is forwarded down to TextTab via the `textareaRef`
  // prop. TextTab attaches it to its <textarea>. When this effect
  // fires, the next-frame callback runs after React has mounted
  // TextTab and committed its ref — so textareaRef.current is
  // populated by the time we call .focus().
  // ---------------------------------------------------------------
  const textareaRef = useRef(null);
  const lastFocusTokenRef = useRef(textEditFocusToken ?? 0);
  useEffect(() => {
    if (textEditFocusToken == null) return;
    if (textEditFocusToken === lastFocusTokenRef.current) return;
    lastFocusTokenRef.current = textEditFocusToken;
    // requestAnimationFrame defers past the current commit — if the
    // same pencil click that bumped this token ALSO switched
    // activeTab to 'text', TextTab is mounting on the same render
    // and its textarea ref isn't attached yet at the moment React
    // fires this effect. Waiting one frame guarantees the textarea
    // is in the DOM and the ref is set.
    const rafId = window.requestAnimationFrame(() => {
      textareaRef.current?.focus();
    });
    return () => window.cancelAnimationFrame(rafId);
  }, [textEditFocusToken]);

  // The Text tab's edit mode is gated on `editingTextElement` (NOT
  // `selectedElement`) so it only activates when the user has
  // explicitly entered text-editing mode via the pencil affordance
  // (desktop) or the Edit-text toolbar button (mobile). Selection
  // alone is no longer enough — see the Change 7 follow-up in
  // [[Refinements_2026-05-20_Part2]] for the rationale.
  const selectedTextElement = editingTextElement?.type === 'text' ? editingTextElement : null;

  // NOTE: the auto-switch-to-Text-tab effect that used to live here
  // moved up to App.jsx — the pencil/Edit-text handler now sets the
  // active tab directly via the controlled API. The behaviour is
  // identical from the user's perspective (Text tab pops to the
  // front on edit gesture, stays put when selection alone changes)
  // but the routing is one place instead of two. See Change 7
  // follow-up in [[Refinements_2026-05-20_Part2]].

  const renderTabContent = () => {
    switch (activeTab) {
      case 'upload':   return <UploadTab onAddImage={onAddImage} recentUploads={recentUploads} onRemoveUpload={onRemoveUpload} />;
      case 'stickers': return <StickersTab onAddSticker={onAddSticker} />;
      case 'emoji':    return <EmojiTab onAddSticker={onAddSticker} />;
      case 'text':     return (
        <TextTab
          onAddText={onAddText}
          selectedTextElement={selectedTextElement}
          onUpdateSelected={onUpdateSelected}
          onCommit={onCommit}
          recentColors={recentColors}
          onPickColor={onPickColor}
          recentFonts={recentFonts}
          onPickFont={onPickFont}
          textareaRef={textareaRef}
          /* Bug 3 fix — forward the full canvas elements list so the
           * TextTab can derive sensible draft defaults from any text
           * already on the canvas. The tab filters to text-typed
           * elements and checks for a shared style; when found, the
           * draft inherits that style's font / fill / outline. */
          elements={elements}
        />
      );
      default: return null;
    }
  };

  return (
    <aside className="rp" aria-label="Customization options">
      <div className="rp__scroll">
        <ShirtOptionsPanel
          selectedColorId={shirtColorId}
          onColorChange={onShirtColorChange}
          selectedSize={shirtSize}
          onSizeChange={onShirtSizeChange}
          price={basePrice}
        />

        <section className="rp__tools" aria-labelledby="rp-tools-heading">
          <h2 id="rp-tools-heading" className="visually-hidden">Design tools</h2>

          <div className="rp__tabs" role="tablist" aria-label="Design tools">
            {TABS.map((tab) => {
              const Icon = tab.icon;
              const isActive = activeTab === tab.id;
              return (
                <button
                  key={tab.id}
                  role="tab"
                  aria-selected={isActive}
                  aria-controls={`tab-panel-${tab.id}`}
                  id={`tab-${tab.id}`}
                  className={`rp__tab${isActive ? ' is-active' : ''}`}
                  onClick={() => setActiveTab(tab.id)}
                >
                  <Icon />
                  <span>{tab.label}</span>
                </button>
              );
            })}
          </div>

          <div
            role="tabpanel"
            id={`tab-panel-${activeTab}`}
            aria-labelledby={`tab-${activeTab}`}
            className="rp__tabpanel"
          >
            {renderTabContent()}
          </div>
        </section>

        {/* Layers section — permanent surface beneath the tools card. The
            LayersPanel renders its own visible "Layers (N)" titlebar and
            handles the empty-state message internally, so this section
            just provides a matching card chrome and a screen-reader
            heading. */}
        <section className="rp__layers" aria-labelledby="rp-layers-heading">
          <h2 id="rp-layers-heading" className="visually-hidden">Layers</h2>
          <LayersPanel
            elements={elements}
            selectedId={selectedElement?.id ?? null}
            selectedIds={selectedIds}
            onSelect={onSelectElement}
            onToggleInSelection={onToggleInSelection}
            onDelete={onDeleteElement}
            onDeleteMany={onDeleteMany}
            onReorder={onReorderElement}
            onDuplicate={onDuplicateElement}
            onUpdate={onUpdateElement}
          />
        </section>
      </div>

      <div className="rp__cart-bar">
        {/* Reason text for the disabled cart, when present. Rendered as a
            sibling of the button rather than a tooltip because (a) the
            primary surface for the warning is already the canvas-frame
            chip, this is a follow-up affordance for the user who has
            scrolled to the cart, and (b) tooltips are unreliable on
            touch. The button's `disabled` and `aria-disabled` attributes
            give assistive tech the actionable signal. */}
        {cartDisabled && cartDisabledReason && (
          <p className="rp__cart-reason" role="status">
            {cartDisabledReason}
          </p>
        )}

        <button
          type="button"
          className={`rp__add-to-cart${cartDisabled ? ' is-disabled' : ''}`}
          onClick={onAddToCart}
          disabled={cartDisabled}
          aria-disabled={cartDisabled}
          title={cartDisabled ? cartDisabledReason || undefined : undefined}
        >
          <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true">
            <path d="M12 21s-7-4.5-9.3-9A5.4 5.4 0 0 1 12 5.5 5.4 5.4 0 0 1 21.3 12C19 16.5 12 21 12 21z" />
          </svg>
          <span>Add to Cart</span>
          <span className="rp__add-to-cart-price">{formatUsd(totalPrice)}</span>
        </button>
      </div>
    </aside>
  );
}

/* ─── Inline icon set — kept here so each tab definition stays declarative ─── */

function UploadIcon() {
  return (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
      <path d="M17 8l-5-5-5 5" />
      <path d="M12 3v12" />
    </svg>
  );
}
function StickersIcon() {
  return (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M14.5 2.5a8 8 0 1 1-12 12L14.5 2.5z" />
      <path d="M14.5 2.5L21.5 9.5a8 8 0 0 1-7 7" />
    </svg>
  );
}
function EmojiIcon() {
  return (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <circle cx="12" cy="12" r="9" />
      <path d="M8 14s1.5 2 4 2 4-2 4-2" />
      <path d="M9 9h.01" />
      <path d="M15 9h.01" />
    </svg>
  );
}
function TextIcon() {
  return (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M4 7V5h16v2" />
      <path d="M9 5v14" />
      <path d="M15 5v14" />
      <path d="M7 19h4" />
      <path d="M13 19h4" />
    </svg>
  );
}
