import { createAnimations } from '@tamagui/animations-reanimated'
import { defaultConfig } from '@tamagui/config/v5'
import type { FillInFont, GenericFont } from '@tamagui/core'
import { createFont, createTamagui } from 'tamagui'
import {
  ACCENT_VARIANT_IDS,
  ACCENT_VARIANTS,
  type AccentVariantSpec,
} from './src/lib/accentVariants'

// Halflight type ramp. The numeric scale ($1…$16) mirrors the v5 defaults
// so inline `fontSize="$3"` consumers keep rendering at their current size;
// the string keys (`$label`, `$helper`, …) are semantic aliases consumed by
// the `Field*` / `Section*` primitives so the coupling between "a field
// label is 16pt" and "FieldLabel picks $5" becomes explicit in one place
// instead of an undocumented convention across the primitive files.
//
// Add new aliases here (not inside the primitive files) so the ramp stays
// the single source of truth.
const fontSizes = {
  1: 12,
  2: 13,
  3: 14,
  4: 15,
  true: 15,
  5: 16,
  6: 18,
  7: 22,
  8: 26,
  9: 30,
  10: 40,
  11: 46,
  12: 52,
  13: 60,
  14: 70,
  15: 85,
  16: 100,
  // Form-surface aliases (existing — consumed by Field* / Section* primitives).
  label: 16,
  title: 16,
  helper: 14,
  error: 14,
  sectionHeader: 14,
  // User-content vocabulary. These are the canonical keys for Typography
  // primitives (BodyText, DisplayName, etc.). Numeric scale stays intact for
  // backward-compatible inline `fontSize="$5"` consumers in chrome layout.
  micro: 13, // overlay badges, per-media timestamps
  caption: 14, // meta rows, counts, "2h ago"
  footnote: 15, // secondary body, comment metadata
  body: 16, // THE user-content body size — captions, bios, comments
  bodyEm: 16, // body with emphasis (bold), same size
  subhead: 17, // UI labels, list-item titles
  headline: 19, // display names, post-header account name
  title3: 22, // section titles inside screens
  title2: 24, // screen-level sub-headers
  title1: 30, // screen-level main headers
  display: 36, // hero moments (onboarding, empty states)
} as const

const bodyLineHeight = (size: number) => {
  const ratio = 1.5 - Math.max(0, (size - 20) * 0.004)
  return Math.round(size * ratio)
}
const headingLineHeight = (size: number) => Math.round(size * 1.12 + 5)

const mapSize = <T>(fn: (size: number) => T) =>
  Object.fromEntries(Object.entries(fontSizes).map(([k, v]) => [k, fn(v)])) as {
    [K in keyof typeof fontSizes]: T
  }

// Per-size letter spacing. Larger display sizes tighten (−0.2 to −0.6) so the
// headline feels composed rather than spaced-out; very small sizes loosen
// slightly (+0.1 / +0.2) for legibility. Numeric keys stay at 0 to preserve
// current inline-`fontSize="$N"` consumer rendering.
const bodyLetterSpacing = {
  1: 0,
  2: 0,
  3: 0,
  4: 0,
  true: 0,
  5: 0,
  6: 0,
  7: 0,
  8: 0,
  9: 0,
  10: 0,
  11: 0,
  12: 0,
  13: 0,
  14: 0,
  15: 0,
  16: 0,
  label: 0,
  title: 0,
  helper: 0,
  error: 0,
  sectionHeader: 0.3, // uppercase — proper tracking for small-caps feel
  micro: 0.2,
  caption: 0.1,
  footnote: 0,
  body: 0,
  bodyEm: 0,
  subhead: 0,
  headline: -0.1,
  title3: -0.2,
  title2: -0.3,
  title1: -0.4,
  display: -0.6,
} as const

// Inter is loaded via `@expo-google-fonts/inter` in `src/app/_layout.tsx`. On
// Android, RN resolves font weights by family name rather than `fontWeight`, so
// `face` maps each weight to the matching PostScript family. iOS can often
// resolve weights from a single family, but setting `face` is the documented
// cross-platform path.
const interFamily = 'Inter_400Regular'
const interFace = {
  400: { normal: 'Inter_400Regular' },
  500: { normal: 'Inter_500Medium' },
  600: { normal: 'Inter_600SemiBold' },
  700: { normal: 'Inter_700Bold' },
  800: { normal: 'Inter_800ExtraBold' },
} as const

// Cast the `createFont` result through `FillInFont` so every section
// (`weight`, `letterSpacing`, etc.) accepts the full size-key set as a
// token reference — matching v5's behavior. Without it, e.g. `fontWeight="$6"`
// stops type-checking in the few places it's used (admin/autospam, reports)
// because the input `weight` only declares the `1` key.
type HalflightFont = FillInFont<GenericFont, keyof typeof fontSizes>

const bodyFont = createFont({
  family: interFamily,
  face: interFace,
  size: fontSizes,
  lineHeight: mapSize(bodyLineHeight),
  weight: {
    1: '400',
    body: '400',
    bodyEm: '600',
    footnote: '400',
    caption: '400',
    micro: '400',
    subhead: '500',
  },
  letterSpacing: bodyLetterSpacing,
}) as unknown as HalflightFont

const headingFont = createFont({
  family: interFamily,
  face: interFace,
  size: fontSizes,
  lineHeight: mapSize(headingLineHeight),
  weight: {
    0: '600',
    6: '700',
    9: '800',
    headline: '600',
    title3: '700',
    title2: '700',
    title1: '700',
    display: '800',
  },
  letterSpacing: bodyLetterSpacing,
}) as unknown as HalflightFont

// Tapestry weave font. JetBrains Mono is loaded via
// `@expo-google-fonts/jetbrains-mono` in `src/app/_layout.tsx` alongside
// Inter; the family name below matches the PostScript name RN's font
// resolver picks up on both platforms. Tabular nums are inherent to the
// face — consecutive minute rows line up on a column grid without a
// `fontVariant: ['tabular-nums']` opt-in.
//
// Line-height is slightly compressed (~1.15) so a dense ribbon of letters
// reads as one woven surface rather than discrete lines; letter-spacing
// stays at 0 since JetBrains Mono is already optimised for code-density.
const weaveFamily = 'JetBrainsMono_400Regular'

const weaveFont = createFont({
  family: weaveFamily,
  face: { 400: { normal: 'JetBrainsMono_400Regular' } },
  size: fontSizes,
  lineHeight: mapSize((size) => Math.round(size * 1.15)),
  weight: {
    1: '400',
    body: '400',
  },
  letterSpacing: bodyLetterSpacing,
}) as unknown as HalflightFont

const animations = createAnimations({
  '100ms': { type: 'timing', duration: 100 },
  quick: { type: 'spring', damping: 20, mass: 1, stiffness: 250 },
  quicker: { type: 'spring', damping: 20, mass: 1, stiffness: 300 },
  medium: { type: 'spring', damping: 18, mass: 1.1, stiffness: 160 },
  bouncy: { type: 'spring', damping: 9, mass: 0.9, stiffness: 180 },
  lazy: { type: 'spring', damping: 20, mass: 1, stiffness: 60 },
})

// Flat theme definitions following Tamagui v1 conventions.
// Standard TamaguiBaseTheme keys (background, color, borderColor, etc.)
// are supplemented with custom semantic variants (backgroundSecondary, colorTertiary, etc.).
// Each value is a plain color string — Tamagui wraps them in Variables automatically.

// Neutral-grey palette tuned for artists / photographers — Photoshop-chrome
// vibe. Chrome stays neutral; a single electric-blue accent family carries
// all emphatic engagement (like, boost, RSVP, create). Danger crimson is
// reserved for destructive states.
const lightTheme = {
  background: '#D8D8D8',
  backgroundHover: '#CFCFCF',
  backgroundPress: '#C8C8C8',
  backgroundFocus: '#CFCFCF',
  backgroundSecondary: '#C8C8C8',
  backgroundTertiary: '#B8B8B8',
  backgroundInverse: '#2A2A2A',
  backgroundTranslucent: 'rgba(216,216,216,0.72)',
  // Android-only fallback fill for the menu pill. Must be fully opaque:
  // Android's elevation shadow renderer produces hard rectangular artifacts
  // when the backgroundColor has any alpha < 1.
  backgroundMenu: '#C8C8C8',

  color: '#1C1C1C',
  colorHover: '#555555',
  colorPress: '#888888',
  colorFocus: '#555555',
  colorSecondary: '#555555',
  colorTertiary: '#8A8A8A',
  colorInverse: '#F5F5F5',
  colorDanger: '#8B3A3A',
  colorSuccess: '#10b981',

  borderColor: '#A8A8A8',
  borderColorHover: '#9A9A9A',
  borderColorPress: '#9A9A9A',
  borderColorFocus: '#9A9A9A',
  borderColorStrong: '#8C8C8C',
  borderColorInverse: '#F5F5F5',

  // Muted slate — reads as "selected" without introducing chroma.
  interactiveActive: '#5B6B7E',
  // Neutral fill for hover/pressed bg on selectable chrome.
  interactiveHover: '#C0C0C0',

  // Electric-blue accent family. `accent` picks up ~4% green vs pure #0000FF
  // to settle the hue against warm grey backgrounds while preserving the
  // "electric" read. `accentMuted` and `accentTint` are alpha-based so the
  // hue reads as the same blue — the earlier precomputed `mix(accent, bg)`
  // hex values desaturated substantially toward the neutral surface.
  accent: '#0A22FF',
  accentPressed: '#0819CC',
  accentMuted: 'rgba(10,34,255,0.5)',
  accentTint: 'rgba(10,34,255,0.18)',
  accentGlow: 'rgba(10,34,255,0.22)',
  onAccent: '#FFFFFF',

  // Glass-surface tint. Painted on top of the expo-blur layer so menu pills
  // and sticky headers read as frosted chrome without a blue wash. Only
  // applied when blur is active — on surfaces without native blur the
  // parent already paints an opaque fill (`$backgroundMenu` on menu pills,
  // `$background` on the sticky header) and the overlay is skipped.
  //
  // Light-mode wash is ~42% white so bright photo backdrops don't bleed
  // through the pill. At 12% the blur alone could not carry enough
  // luminance to keep `$onSurfaceMuted` icons/labels legible on saturated
  // imagery.
  surfaceGlassTint: 'rgba(255,255,255,0.42)',

  // On-surface icon / text tints for menu chrome. Three tones replace the
  // five-way ad-hoc zoo (`color`, `colorTertiary`, `interactiveActive`,
  // `colorInverse`, per-selectable). Consumers pick based on state, not
  // structural role.
  onSurfacePrimary: '#1C1C1C',
  onSurfaceMuted: '#555555',
  onSurfaceActive: '#0A22FF',

  // Tapestry corrective-class span colour (`u`/`d`/`x` letters: unfollow,
  // delete, report). A desaturated warm grey-brown reads as "off" against
  // the neutral palette without crossing into alarm-red — the corrective
  // class is meant to register as friction, not danger.
  weaveCorrective: '#8B6F5C',

  shadowColor: 'rgba(0,0,0,0.08)',
  shadowColorHover: 'rgba(0,0,0,0.12)',
  shadowColorPress: 'rgba(0,0,0,0.16)',
  shadowColorFocus: 'rgba(0,0,0,0.12)',
}

const darkTheme = {
  background: '#2A2A2A',
  backgroundHover: '#3A3A3A',
  backgroundPress: '#454545',
  backgroundFocus: '#3A3A3A',
  backgroundSecondary: '#3A3A3A',
  backgroundTertiary: '#454545',
  backgroundInverse: '#F2F2F2',
  backgroundTranslucent: 'rgba(42,42,42,0.72)',
  // Android-only fallback fill for the menu pill. See light theme note.
  backgroundMenu: '#3A3A3A',

  color: '#DCDCDC',
  colorHover: '#B8B8B8',
  colorPress: '#8A8A8A',
  colorFocus: '#B8B8B8',
  colorSecondary: '#B8B8B8',
  colorTertiary: '#8A8A8A',
  colorInverse: '#1C1C1C',
  colorDanger: '#C15C5C',
  colorSuccess: '#10b981',

  borderColor: '#454545',
  borderColorHover: '#5A5A5A',
  borderColorPress: '#5A5A5A',
  borderColorFocus: '#5A5A5A',
  borderColorStrong: '#707070',
  borderColorInverse: '#1C1C1C',

  interactiveActive: '#7A8B9E',
  interactiveHover: '#454545',

  // Dark-theme accent family. Pure #0000FF sits at 3.7:1 against #2A2A2A —
  // below AA; #3A52FF raises that to ~4.8:1 while still reading as the same
  // blue. `accentMuted` / `accentTint` are alpha-based; the 50%-alpha blue
  // keeps the hue, and the 18%-alpha tint is for subtle chip fills.
  accent: '#3A52FF',
  accentPressed: '#5A72FF',
  accentMuted: 'rgba(58,82,255,0.5)',
  accentTint: 'rgba(58,82,255,0.18)',
  accentGlow: 'rgba(58,82,255,0.30)',
  onAccent: '#FFFFFF',

  // Glass-surface tint — see light-theme comment. Dark surfaces previously
  // used a 22% black wash which left bright photo backdrops bleeding through
  // the pill and turning the muted icon/label tint to a low-contrast grey-
  // on-grey. 42% mirrors the light theme's wash strength so menu chrome
  // stays legible regardless of what's underneath.
  surfaceGlassTint: 'rgba(0,0,0,0.42)',

  // `onSurfaceMuted` lifted from `#B8B8B8` to `#D0D0D0` — at the previous
  // value the tint sat on the wrong side of AA against the now-darker glass
  // pill on bright backdrops. The new value reads cleanly against both the
  // pill's translucent backdrop and the solid Android `backgroundMenu` fill.
  onSurfacePrimary: '#DCDCDC',
  onSurfaceMuted: '#D0D0D0',
  onSurfaceActive: '#6E82FF',

  // Tapestry corrective-class span colour. Lighter on dark to keep the
  // warm-grey character without dropping below AA against #2A2A2A —
  // light-mode #8B6F5C inverted toward the upper-mid range.
  weaveCorrective: '#B89A87',

  shadowColor: 'rgba(0,0,0,0.3)',
  shadowColorHover: 'rgba(0,0,0,0.4)',
  shadowColorPress: 'rgba(0,0,0,0.5)',
  shadowColorFocus: 'rgba(0,0,0,0.4)',
}

// ──────────────────────────────────────────────────────────────────────────
// Per-account accent variants.
//
// Eight named palettes (defined in src/lib/accentVariants.ts) that compose
// with light/dark via Tamagui sub-themes. `<Theme name="dark"><Theme
// name="accent3">` resolves to `dark_accent3`, which only ships the accent-
// family overrides — every other token falls back to `dark`. The active-
// account hook in `src/app/_layout.tsx` reads `AccountSummary.accentVariant`
// and swaps the inner `<Theme>` on switch.
//
// Variant 1 is the existing electric-blue palette and is registered as a
// pass-through sub-theme so the wrapper can apply unconditionally (rather
// than branching on `n === 1`).
// ──────────────────────────────────────────────────────────────────────────

type AccentPalette = {
  accent: string
  accentPressed: string
  accentMuted: string
  accentTint: string
  accentGlow: string
  onAccent: string
  onSurfaceActive: string
}

// `#RRGGBB` + alpha → `rgba(r,g,b,a)` string. Same shape as the inline
// `rgba(10,34,255,0.18)` etc. in the base themes; pulling it into a helper
// keeps the eight palettes spec-readable.
function alphaHex(hex: string, a: number): string {
  const m = hex.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i)
  if (!m) return hex
  const r = parseInt(m[1], 16)
  const g = parseInt(m[2], 16)
  const b = parseInt(m[3], 16)
  return `rgba(${r},${g},${b},${a})`
}

function buildAccentPalette(
  v: AccentVariantSpec,
  scheme: 'light' | 'dark'
): AccentPalette {
  const base = scheme === 'light' ? v.light : v.dark
  const pressed = scheme === 'light' ? v.lightPressed : v.darkPressed
  const glowAlpha = scheme === 'light' ? 0.22 : 0.30
  return {
    accent: base,
    accentPressed: pressed,
    accentMuted: alphaHex(base, 0.5),
    accentTint: alphaHex(base, 0.18),
    accentGlow: alphaHex(base, glowAlpha),
    onAccent: scheme === 'light' ? v.onAccent : v.onAccentDark,
    // onSurfaceActive: same hue, lifted to the pressed sibling on dark so it
    // doesn't read as the "selected fill" when painted as a hover/focus tint
    // (matches the base `dark.onSurfaceActive: '#6E82FF'` precedent).
    onSurfaceActive: scheme === 'light' ? base : pressed,
  }
}

const accentSubThemes = Object.fromEntries(
  ACCENT_VARIANT_IDS.flatMap((n) => [
    [`light_accent${n}`, buildAccentPalette(ACCENT_VARIANTS[n], 'light')],
    [`dark_accent${n}`, buildAccentPalette(ACCENT_VARIANTS[n], 'dark')],
  ])
)

// Breakpoints sized for phone → tablet. Tamagui evaluates these as boolean
// variants on every styled component, so keep the set minimal and use
// `$gtSm`/`$gtMd` at call sites instead of reaching for useWindowDimensions.
const media = {
  sm: { maxWidth: 640 },
  gtSm: { minWidth: 641 },
  md: { maxWidth: 800 },
  gtMd: { minWidth: 801 },
  lg: { maxWidth: 1024 },
  gtLg: { minWidth: 1025 },
  short: { maxHeight: 820 },
  tall: { minHeight: 821 },
} as const

export const config = createTamagui({
  ...defaultConfig,
  animations,
  media,
  fonts: {
    body: bodyFont,
    heading: headingFont,
    weave: weaveFont,
  },
  settings: {
    ...defaultConfig.settings,
    onlyAllowShorthands: false,
    allowedStyleValues: false,
    // Off because the Appearance picker lets users force light/dark regardless
    // of the OS scheme; fastSchemeChange requires defaultTheme to always match
    // the system scheme. Trade-off is a one-frame slower OS dark-mode toggle.
    fastSchemeChange: false,
  },
  shorthands: {
    ...defaultConfig.shorthands,
    w: 'width',
    h: 'height',
    o: 'opacity',
    ta: 'textAlign',
  } as const,
  themes: {
    ...defaultConfig.themes,
    light: { ...defaultConfig.themes.light, ...lightTheme },
    dark: { ...defaultConfig.themes.dark, ...darkTheme },
    ...accentSubThemes,
  },
})

export default config

export type Conf = typeof config

declare module 'tamagui' {
  interface TamaguiCustomConfig extends Conf {}
}
