/**
 * Tabs: sectioned content with keyboard navigation. Tier-2. The parent
 * <ui-tabs> owns `value` + `orientation`; triggers and panels read from
 * it via `closest('ui-tabs')` and re-render when value changes.
 *
 * APG pattern: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
 *
 * shadcn parity:
 *   Tabs           → <ui-tabs value orientation>
 *   TabsList       → <ui-tabs-list variant>
 *   TabsTrigger    → <ui-tabs-trigger value>
 *   TabsContent    → <ui-tabs-content value>
 *
 * Usage:
 *   <ui-tabs value="account">
 *     <ui-tabs-list>
 *       <ui-tabs-trigger value="account">Account</ui-tabs-trigger>
 *       <ui-tabs-trigger value="password">Password</ui-tabs-trigger>
 *     </ui-tabs-list>
 *     <ui-tabs-content value="account">…</ui-tabs-content>
 *     <ui-tabs-content value="password">…</ui-tabs-content>
 *   </ui-tabs>
 *
 * Attributes on <ui-tabs>:
 *   `value`:       string (reflected, controlled). Active tab value.
 *   `orientation`: "horizontal" (default) | "vertical".
 *
 * Attributes on <ui-tabs-list>:
 *   `variant`: "default" (default, pill-on-muted) | "underline".
 *
 * Attributes on <ui-tabs-trigger> / <ui-tabs-content>:
 *   `value`: string. Identifier this trigger / panel pair shares.
 *
 * Events:
 *   `ui-value-change` on <ui-tabs>: `{ detail: { value } }` after a change.
 *
 * Keyboard (on focused trigger):
 *   ArrowRight / ArrowLeft   next / previous (horizontal)
 *   ArrowDown / ArrowUp      next / previous (vertical)
 *   Home / End               first / last
 *   Enter / Space            activate (native button activation)
 *
 * Design tokens used: --muted, --muted-foreground, --foreground, --background,
 * --input, --ring.
 */
import { WebComponent, html } from '@webjsdev/core';
import { cn } from '../lib/utils.ts';

// --------------------------------------------------------------------------
// Class helpers
// --------------------------------------------------------------------------

const TABS_BASE = 'group/tabs flex gap-2 data-[orientation=horizontal]:flex-col';

const TABS_LIST_BASE =
  'group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=underline]:rounded-none';

const TABS_LIST_VARIANTS = {
  default: 'bg-muted',
  underline: 'gap-1 bg-transparent',
} as const;

export type TabsListVariant = keyof typeof TABS_LIST_VARIANTS;

/** Optional helper exposing the tabs-list class for advanced overrides. */
export function tabsListClass(opts: { variant?: TabsListVariant } = {}): string {
  const variant = opts.variant ?? 'default';
  return cn(TABS_LIST_BASE, TABS_LIST_VARIANTS[variant]);
}

// The trigger is rendered as a native <button role="tab">, which gives
// us cursor-pointer + Enter/Space activation + focus for free. The class
// here is essentially shadcn's tabs.tsx trigger output.
const TABS_TRIGGER_CLASS = [
  "relative inline-flex h-[calc(100%-1px)] flex-1 cursor-pointer select-none items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=underline]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
  // These mirror shadcn's tabs.tsx (modulo variant=line → variant=underline).
  // Light-mode active state uses bg-background + shadow only; dark mode
  // additionally borders. Adding light-mode border made the underline
  // variant look like outline and made default look like two buttons.
  'group-data-[variant=underline]/tabs-list:bg-transparent group-data-[variant=underline]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=underline]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=underline]/tabs-list:data-[state=active]:bg-transparent',
  'data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground',
  'after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=underline]/tabs-list:data-[state=active]:after:opacity-100',
].join(' ');

const TABS_CONTENT_CLASS = 'flex-1 outline-none';

// --------------------------------------------------------------------------
// <ui-tabs> owns active `value` + `orientation`. Children read its state
// via closest('ui-tabs'); when value changes, the parent fires a
// `tabs-value-change-internal` event that descendants listen for via
// the bubbling event on their own host. Each descendant then calls
// requestUpdate() to re-render against the new parent value.
// --------------------------------------------------------------------------

export class UiTabs extends WebComponent {
  static properties = {
    value: { type: String, reflect: true },
    orientation: { type: String, reflect: true },
  };
  declare value: string;
  declare orientation: 'horizontal' | 'vertical';

  constructor() {
    super();
    this.value = '';
    this.orientation = 'horizontal';
  }

  render() {
    return html`<div
      data-slot="tabs"
      data-orientation=${this.orientation}
      class=${TABS_BASE}
    ><slot></slot></div>`;
  }

  updated(changedProperties: Map<string, unknown>): void {
    if (!changedProperties.has('value')) return;
    const prev = (changedProperties.get('value') ?? '') as string;
    // Skip the initial undefined -> '' (or '' -> '') no-op set; only
    // dispatch + broadcast on real value transitions.
    if (prev === '' && this.value === '') return;
    queueMicrotask(() => {
      this.dispatchEvent(
        new CustomEvent('ui-value-change', { detail: { value: this.value }, bubbles: true }),
      );
      this._broadcast();
    });
  }

  _broadcast(): void {
    this.querySelectorAll<WebComponent>(
      'ui-tabs-trigger, ui-tabs-content',
    ).forEach((el) => el.requestUpdate?.());
  }
}
UiTabs.register('ui-tabs');

// --------------------------------------------------------------------------
// <ui-tabs-list>
// --------------------------------------------------------------------------

export class UiTabsList extends WebComponent {
  static properties = {
    variant: { type: String, reflect: true },
  };
  declare variant: TabsListVariant;

  constructor() {
    super();
    this.variant = 'default';
  }

  render() {
    return html`<div
      data-slot="tabs-list"
      role="tablist"
      data-variant=${this.variant}
      class=${tabsListClass({ variant: this.variant })}
    ><slot></slot></div>`;
  }
}
UiTabsList.register('ui-tabs-list');

// --------------------------------------------------------------------------
// <ui-tabs-trigger value="...">
// --------------------------------------------------------------------------

export class UiTabsTrigger extends WebComponent {
  static properties = {
    value: { type: String, reflect: true },
  };
  declare value: string;

  constructor() {
    super();
    this.value = '';
  }

  // render() runs server-side too. webjs resolves closest() at SSR against
  // the enclosing-element ancestor chain, so the active tab is marked in the
  // first paint (no hydration flash). The typeof guard stays defensive.
  get _tabs(): UiTabs | null {
    if (typeof this.closest !== 'function') return null;
    return this.closest('ui-tabs') as UiTabs | null;
  }

  render() {
    const tabs = this._tabs;
    const active = !!tabs && tabs.value === this.value && this.value !== '';
    return html`<button
      type="button"
      role="tab"
      data-slot="tabs-trigger"
      data-state=${active ? 'active' : 'inactive'}
      aria-selected=${String(active)}
      tabindex=${active ? '0' : '-1'}
      class=${TABS_TRIGGER_CLASS}
      @click=${this._onClick}
      @keydown=${this._onKeyDown}
    ><slot></slot></button>`;
  }

  _onClick = (): void => {
    if (this.value) this._tabs?.setAttribute('value', this.value);
  };

  _onKeyDown = (e: KeyboardEvent): void => {
    const tabs = this._tabs;
    if (!tabs) return;
    const orientation = tabs.orientation;
    const triggers = Array.from(tabs.querySelectorAll<UiTabsTrigger>('ui-tabs-trigger'));
    const idx = triggers.indexOf(this);
    const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';
    const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';

    let target: UiTabsTrigger | null = null;
    if (e.key === nextKey) target = triggers[(idx + 1) % triggers.length] ?? null;
    else if (e.key === prevKey) target = triggers[(idx - 1 + triggers.length) % triggers.length] ?? null;
    else if (e.key === 'Home') target = triggers[0] ?? null;
    else if (e.key === 'End') target = triggers[triggers.length - 1] ?? null;
    // Enter/Space already handled natively by <button>; no preventDefault needed.

    if (target) {
      e.preventDefault();
      const v = target.value;
      if (v) tabs.setAttribute('value', v);
      target.focus();
    }
  };
}
UiTabsTrigger.register('ui-tabs-trigger');

// --------------------------------------------------------------------------
// <ui-tabs-content value="...">
// --------------------------------------------------------------------------

export class UiTabsContent extends WebComponent {
  static properties = {
    value: { type: String, reflect: true },
  };
  declare value: string;

  constructor() {
    super();
    this.value = '';
  }

  get _tabs(): UiTabs | null {
    if (typeof this.closest !== 'function') return null;
    return this.closest('ui-tabs') as UiTabs | null;
  }

  render() {
    const tabs = this._tabs;
    const active = !!tabs && tabs.value === this.value && this.value !== '';
    // The host needs to be hidden when inactive so its rendered <section>
    // is removed from layout (light DOM has no :host CSS to scope this).
    // Use the native `hidden` IDL property rather than imperative
    // setAttribute, so it reads as a property assignment.
    this.hidden = !active;
    return html`<section
      data-slot="tabs-content"
      role="tabpanel"
      tabindex="0"
      data-state=${active ? 'active' : 'inactive'}
      class=${TABS_CONTENT_CLASS}
    ><slot></slot></section>`;
  }
}
UiTabsContent.register('ui-tabs-content');
