import {
  ref,
  reactive,
  computed,
  watch,
  watchEffect,
  onMounted,
  onUnmounted,
  nextTick,
  provide,
  inject,
  h,
  render,
  createVNode,
  Suspense,
  type App,
  type Component
} from 'vue'
import { setActivePinia, type Pinia } from 'pinia'
import { useCanvasStore } from '../stores/canvas'
import { useThemeStore } from '../stores/theme'
import { useWindowsStore } from '../stores/windows'
import WindowContainer from '../components/WindowContainer.vue'
import { apiFetch } from '@/lib/tauri'

// Uses relative URLs - works with Vite proxy in dev and Traefik in production
const API_URL = ''

// Tipos
export interface VueComponentDefinition {
  id: string
  name: string
  template: string
  setup?: string
  style?: string
  props?: string[]
  imports?: string[]
  tags?: string[]
  status?: 'active' | 'archived'
  created_at?: string
  updated_at?: string
}

// ============================================
// EVENT BUS
// ============================================

type EventCallback = (...args: any[]) => void

class EventBus {
  private events: Map<string, Set<EventCallback>> = new Map()

  on(event: string, callback: EventCallback) {
    if (!this.events.has(event)) {
      this.events.set(event, new Set())
    }
    this.events.get(event)!.add(callback)
    return () => this.off(event, callback)
  }

  off(event: string, callback: EventCallback) {
    this.events.get(event)?.delete(callback)
  }

  emit(event: string, ...args: any[]) {
    this.events.get(event)?.forEach(cb => cb(...args))
  }

  once(event: string, callback: EventCallback) {
    const wrapper = (...args: any[]) => {
      callback(...args)
      this.off(event, wrapper)
    }
    this.on(event, wrapper)
  }
}

export const eventBus = new EventBus()
;(window as any).__eventBus = eventBus

// ============================================
// CSS SCOPING
// ============================================

function generateScopeId(id: string): string {
  return 'v-' + id.replace(/[^a-zA-Z0-9]/g, '').slice(0, 8)
}

function scopeCSS(css: string, scopeId: string): string {
  const scopePrefix = `#canvas-content [data-${scopeId}]`

  return css.replace(
    /([^{}]+)\{([^{}]*)\}/g,
    (_match, selectors: string, rules: string) => {
      const scopedSelectors = selectors
        .split(',')
        .map((selector: string) => {
          selector = selector.trim()
          if (!selector || selector.startsWith('@')) return selector
          return `${scopePrefix} ${selector}`
        })
        .join(', ')

      return `${scopedSelectors} {${rules}}`
    }
  )
}

function injectScopedStyle(css: string, scopeId: string, componentId: string): void {
  const styleId = `style-${componentId}`
  let styleEl = document.getElementById(styleId)

  if (!styleEl) {
    styleEl = document.createElement('style')
    styleEl.id = styleId
    document.head.appendChild(styleEl)
  }

  styleEl.textContent = scopeCSS(css, scopeId)
}

// ============================================
// COMPONENTS API
// ============================================

export const componentsApi = {
  async getAll(opts?: { includeArchived?: boolean; limit?: number }): Promise<VueComponentDefinition[]> {
    const params = new URLSearchParams()
    if (opts?.includeArchived) params.set('include_archived', 'true')
    if (opts?.limit) params.set('limit', String(opts.limit))
    const qs = params.toString()
    const res = await apiFetch(`${API_URL}/api/components${qs ? '?' + qs : ''}`)
    const data = await res.json()
    return data.map((row: any) => ({
      ...row,
      props: JSON.parse(row.props || '[]'),
      imports: JSON.parse(row.imports || '[]'),
      tags: JSON.parse(row.tags || '[]')
    }))
  },

  async getById(id: string): Promise<VueComponentDefinition | null> {
    const res = await apiFetch(`${API_URL}/api/components/${id}`)
    if (!res.ok) return null
    const row = await res.json()
    return {
      ...row,
      props: JSON.parse(row.props || '[]'),
      imports: JSON.parse(row.imports || '[]'),
      tags: JSON.parse(row.tags || '[]')
    }
  },

  async save(component: VueComponentDefinition): Promise<{ success: boolean; id: string }> {
    const res = await apiFetch(`${API_URL}/api/components`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(component)
    })
    return res.json()
  },

  async update(id: string, data: Partial<VueComponentDefinition>): Promise<{ success: boolean }> {
    const res = await apiFetch(`${API_URL}/api/components/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    })
    return res.json()
  },

  async restore(id: string): Promise<{ success: boolean }> {
    return this.update(id, { status: 'active' })
  },

  async delete(id: string): Promise<{ success: boolean; warning?: string }> {
    const res = await apiFetch(`${API_URL}/api/components/${id}`, { method: 'DELETE' })
    return res.json()
  },

  async archiveAll(): Promise<{ success: boolean }> {
    const res = await apiFetch(`${API_URL}/api/components`, { method: 'DELETE' })
    return res.json()
  }
}

// ============================================
// VUE EXPORTS & HELPERS
// ============================================

const vueExports = {
  ref, reactive, computed, watch, watchEffect,
  onMounted, onUnmounted, nextTick, provide, inject, h
}

function getCanvasStore() {
  const globalPinia = (window as any).__pinia as Pinia | undefined
  if (globalPinia) setActivePinia(globalPinia)
  return useCanvasStore()
}

function getThemeStore() {
  const globalPinia = (window as any).__pinia as Pinia | undefined
  if (globalPinia) setActivePinia(globalPinia)
  return useThemeStore()
}

function getWindowsStore() {
  const globalPinia = (window as any).__pinia as Pinia | undefined
  if (globalPinia) setActivePinia(globalPinia)
  return useWindowsStore()
}

const dynamicHelpers = {
  $emit: (event: string, ...args: any[]) => eventBus.emit(event, ...args),
  $on: (event: string, cb: EventCallback) => eventBus.on(event, cb),
  $once: (event: string, cb: EventCallback) => eventBus.once(event, cb),
  $off: (event: string, cb: EventCallback) => eventBus.off(event, cb),
  $fetch: fetch.bind(window),
  $components: {
    load: (id: string) => componentsApi.getById(id),
    list: () => componentsApi.getAll(),
    save: (comp: VueComponentDefinition) => componentsApi.save(comp),
  },
  $theme: {
    getVariable: (name: string) => getComputedStyle(document.documentElement).getPropertyValue(`--${name}`).trim(),
    setVariable: (name: string, value: string) => document.documentElement.style.setProperty(`--${name}`, value),
    getTokens: () => getThemeStore().designTokens,
    getActiveTheme: () => getThemeStore().activeTheme,
    getVariables: () => getThemeStore().currentVariables,
  },
  useCanvasStore: getCanvasStore,
  useThemeStore: getThemeStore,
  $nextTick: nextTick,
}

// ============================================
// BUILD COMPONENT
// ============================================

export function buildComponent(definition: VueComponentDefinition): Component {
  const imports = definition.imports || ['ref', 'reactive', 'computed']

  const vueImports: Record<string, any> = {}
  imports.forEach(i => {
    if (i in vueExports) vueImports[i] = (vueExports as any)[i]
  })

  const isAsync = definition.setup ? /\bawait\b/.test(definition.setup) : false

  let setupFn: Function | null = null
  if (definition.setup) {
    try {
      const fnBody = isAsync
        ? `return (async () => { ${definition.setup} })()`
        : definition.setup

      setupFn = new Function(
        ...Object.keys(vueImports),
        'props',
        'ctx',
        ...Object.keys(dynamicHelpers),
        fnBody
      )
    } catch (e) {
      console.error('[DynamicComponent] Error parsing setup:', e)
    }
  }

  const componentDef: Component = {
    name: definition.name,
    props: definition.props || [],
    template: definition.template
  }

  const executeSetup = (props: any, ctx: any) => {
    if (!setupFn) return {}
    return setupFn(
      ...Object.values(vueImports),
      props,
      ctx,
      ...Object.values(dynamicHelpers)
    )
  }

  if (isAsync) {
    componentDef.async = true
    componentDef.setup = async (props: any, ctx: any) => await executeSetup(props, ctx)
  } else {
    componentDef.setup = executeSetup
  }

  return componentDef
}

// ============================================
// RENDER COMPONENT
// ============================================

const renderedContainers: Map<string, HTMLElement> = new Map()

export interface LayoutOptions {
  x?: number
  y?: number
  width?: number
  height?: number
}

export function renderInlineComponent(
  definition: VueComponentDefinition,
  target: HTMLElement,
  props: Record<string, any> = {},
  append: boolean = false,
  layout?: LayoutOptions
): { unmount: () => void } {
  const scopeId = generateScopeId(definition.id)
  const windowsStore = getWindowsStore()

  // Registrar ventana en el store
  const windowState = windowsStore.register(definition.id, {
    title: definition.name,
    x: layout?.x,
    y: layout?.y,
    width: layout?.width ?? 400,
    height: layout?.height ?? 300
  })

  const container = document.createElement('div')
  container.id = `inline-${definition.id}`
  container.className = 'dynamic-component-wrapper'
  container.setAttribute(`data-${scopeId}`, '')

  // Siempre hacer append para ventanas flotantes
  if (append) {
    target.appendChild(container)
  } else {
    // En modo replace, limpiar contenedor anterior si existe
    const oldContainer = renderedContainers.get(definition.id)
    if (oldContainer) {
      render(null, oldContainer)
      oldContainer.remove()
    }
    target.appendChild(container)
  }

  if (definition.style) {
    injectScopedStyle(definition.style, scopeId, definition.id)
  }

  const isAsync = definition.setup ? /\bawait\b/.test(definition.setup) : false
  const component = buildComponent(definition)

  // Función unmount que se llamará al cerrar la ventana
  const unmount = () => {
    render(null, container)
    renderedContainers.delete(definition.id)
    windowsStore.remove(definition.id)
    container.remove()
    document.getElementById(`style-${definition.id}`)?.remove()
  }

  // Crear el componente interno
  const innerComponent = isAsync
    ? createVNode(Suspense, null, {
        default: () => createVNode(component, props),
        fallback: () => createVNode('div', { class: 'loading' }, 'Loading...')
      })
    : createVNode(component, props)

  // Envolver en WindowContainer
  const windowVNode = createVNode(
    WindowContainer,
    {
      id: definition.id,
      title: definition.name,
      x: windowState.x,
      y: windowState.y,
      width: windowState.width,
      height: windowState.height,
      onClose: unmount,
      onMove: (pos: { x: number; y: number }) => {
        windowsStore.updatePosition(definition.id, pos.x, pos.y)
      },
      onResize: (size: { width: number; height: number }) => {
        windowsStore.updateSize(definition.id, size.width, size.height)
      },
      onFocus: () => {
        windowsStore.bringToFront(definition.id)
      }
    },
    { default: () => innerComponent }
  )

  const mainApp = (window as any).__vueApp as App | undefined
  if (mainApp?._context) windowVNode.appContext = mainApp._context

  render(windowVNode, container)
  renderedContainers.set(definition.id, container)

  return { unmount }
}
