/**
 * SplitPane - Resizable split panel component
 * @countermeasure/web-components/components/split-pane
 */

import { type PaneConfig, type SplitPaneConfig } from './types'

export type { PaneConfig, SplitOrientation, SplitPaneConfig } from './types'

/** Resolve a container reference to an HTMLElement */
function resolveContainer(container: HTMLElement | string): HTMLElement {
  if (typeof container === 'string') {
    const el = document.querySelector<HTMLElement>(container)
    if (!el) {
      throw new Error(`SplitPane container not found: ${container}`)
    }
    return el
  }
  return container
}

/** Parse a size value to a percentage (0-100) given a total pixel dimension */
function parseSizeToPercent(size: number | string | undefined, totalPx: number): number {
  if (size === undefined) {
    return 50
  }

  if (typeof size === 'number') {
    if (totalPx > 0) {
      return (size / totalPx) * 100
    }
    return 50
  }

  // String: could be "50%" or "200px"
  if (size.endsWith('%')) {
    const parsed = parseFloat(size)
    if (!Number.isNaN(parsed)) {
      return parsed
    }
    return 50
  }

  if (size.endsWith('px')) {
    const parsed = parseFloat(size)
    if (!Number.isNaN(parsed) && totalPx > 0) {
      return (parsed / totalPx) * 100
    }
    return 50
  }

  const parsed = parseFloat(size)
  if (!Number.isNaN(parsed)) {
    return parsed
  }

  return 50
}

interface PaneState {
  config: PaneConfig
  element: HTMLElement
  sizePercent: number
  collapsed: boolean
  savedSize: number
}

export class SplitPane {
  private readonly container: HTMLElement
  private readonly config: SplitPaneConfig
  private wrapperEl: HTMLElement | null = null
  private readonly paneStates: [PaneState, PaneState]
  private isDragging = false

  // Bound handlers for cleanup
  private readonly handleMouseMove: (e: MouseEvent) => void
  private readonly handleMouseUp: () => void

  constructor(config: SplitPaneConfig) {
    this.container = resolveContainer(config.container)
    this.config = config

    const orientation = config.orientation ?? 'horizontal'

    // Compute initial sizes
    const containerRect = this.container.getBoundingClientRect()
    const totalPx = orientation === 'horizontal' ? containerRect.width : containerRect.height

    const [pane0Config, pane1Config] = config.panes
    const firstSize = parseSizeToPercent(pane0Config.defaultSize, totalPx)
    const secondSize = 100 - firstSize

    this.paneStates = [
      {
        config: pane0Config,
        element: document.createElement('div'),
        sizePercent: firstSize,
        collapsed: false,
        savedSize: firstSize,
      },
      {
        config: pane1Config,
        element: document.createElement('div'),
        sizePercent: secondSize,
        collapsed: false,
        savedSize: secondSize,
      },
    ]

    this.handleMouseMove = (e: MouseEvent) => {
      this.onMouseMove(e)
    }
    this.handleMouseUp = () => {
      this.onMouseUp()
    }

    this.buildDom()
  }

  /** Get the HTMLElement for a pane by id */
  getPane(id: string): HTMLElement | null {
    for (const state of this.paneStates) {
      if (state.config.id === id) {
        return state.element
      }
    }
    return null
  }

  /** Collapse a pane to zero size */
  collapse(id: string): void {
    const state = this.findPaneState(id)
    if (!state) {
      return
    }

    if (state.config.collapsible !== true) {
      return
    }

    if (state.collapsed) {
      return
    }

    state.savedSize = state.sizePercent
    state.collapsed = true
    state.element.classList.add('cmm-split__pane--collapsed')

    const other = this.getOtherPane(id)
    if (other) {
      other.sizePercent += state.sizePercent
    }
    state.sizePercent = 0

    this.applySizes()
  }

  /** Expand a previously collapsed pane */
  expand(id: string): void {
    const state = this.findPaneState(id)
    if (!state) {
      return
    }

    if (!state.collapsed) {
      return
    }

    state.collapsed = false
    state.element.classList.remove('cmm-split__pane--collapsed')

    const restoreSize = state.savedSize
    const other = this.getOtherPane(id)
    if (other) {
      other.sizePercent -= restoreSize
      if (other.sizePercent < 0) {
        other.sizePercent = 0
      }
    }
    state.sizePercent = restoreSize

    this.applySizes()
  }

  /** Set pane sizes by id. Values can be numbers (percent) or strings (e.g. "50%", "200px") */
  setSizes(sizes: Record<string, number | string>): void {
    const orientation = this.config.orientation ?? 'horizontal'
    const containerRect = this.container.getBoundingClientRect()
    const totalPx = orientation === 'horizontal' ? containerRect.width : containerRect.height

    for (const state of this.paneStates) {
      const size = sizes[state.config.id]
      if (size !== undefined) {
        state.sizePercent = parseSizeToPercent(size, totalPx)
        state.collapsed = false
        state.element.classList.remove('cmm-split__pane--collapsed')
      }
    }

    this.applySizes()
  }

  /** Remove all DOM elements and event listeners */
  destroy(): void {
    document.removeEventListener('mousemove', this.handleMouseMove)
    document.removeEventListener('mouseup', this.handleMouseUp)
    this.container.innerHTML = ''
    this.wrapperEl = null
  }

  /** Build the DOM structure for the split pane */
  private buildDom(): void {
    this.container.innerHTML = ''

    const orientation = this.config.orientation ?? 'horizontal'
    const gutterSize = this.config.gutterSize ?? 4

    const wrapper = document.createElement('div')
    wrapper.className = `cmm-split cmm-split--${orientation}`
    wrapper.style.display = 'flex'
    wrapper.style.width = '100%'
    wrapper.style.height = '100%'

    if (orientation === 'vertical') {
      wrapper.style.flexDirection = 'column'
    } else {
      wrapper.style.flexDirection = 'row'
    }

    this.wrapperEl = wrapper

    // First pane
    const pane0El = this.paneStates[0].element
    pane0El.className = 'cmm-split__pane'
    pane0El.dataset.paneId = this.paneStates[0].config.id
    pane0El.style.overflow = 'auto'
    wrapper.appendChild(pane0El)

    // Gutter
    const gutter = document.createElement('div')
    gutter.className = 'cmm-split__gutter'

    if (orientation === 'horizontal') {
      gutter.style.width = `${String(gutterSize)}px`
      gutter.style.cursor = 'col-resize'
    } else {
      gutter.style.height = `${String(gutterSize)}px`
      gutter.style.cursor = 'row-resize'
    }

    gutter.style.flexShrink = '0'
    gutter.addEventListener('mousedown', (e: MouseEvent) => {
      this.onMouseDown(e)
    })
    wrapper.appendChild(gutter)

    // Second pane
    const pane1El = this.paneStates[1].element
    pane1El.className = 'cmm-split__pane'
    pane1El.dataset.paneId = this.paneStates[1].config.id
    pane1El.style.overflow = 'auto'
    wrapper.appendChild(pane1El)

    this.container.appendChild(wrapper)
    this.applySizes()
  }

  /** Apply current size percentages to the pane elements */
  private applySizes(): void {
    const orientation = this.config.orientation ?? 'horizontal'
    const gutterSize = this.config.gutterSize ?? 4

    const [state0, state1] = this.paneStates

    const prop = orientation === 'horizontal' ? 'width' : 'height'

    state0.element.style[prop] =
      `calc(${String(state0.sizePercent)}% - ${String(gutterSize / 2)}px)`
    state1.element.style[prop] =
      `calc(${String(state1.sizePercent)}% - ${String(gutterSize / 2)}px)`
    state0.element.style.flexShrink = '0'
    state1.element.style.flexShrink = '0'

    if (this.config.onResize !== undefined) {
      this.config.onResize(state0.config.id, state0.sizePercent)
      this.config.onResize(state1.config.id, state1.sizePercent)
    }
  }

  /** Handle gutter mousedown to start drag */
  private onMouseDown(e: MouseEvent): void {
    e.preventDefault()
    this.isDragging = true
    document.addEventListener('mousemove', this.handleMouseMove)
    document.addEventListener('mouseup', this.handleMouseUp)
  }

  /** Handle mouse move during gutter drag */
  private onMouseMove(e: MouseEvent): void {
    if (!this.isDragging) {
      return
    }

    if (!this.wrapperEl) {
      return
    }

    const orientation = this.config.orientation ?? 'horizontal'
    const rect = this.wrapperEl.getBoundingClientRect()

    let ratio: number
    if (orientation === 'horizontal') {
      const x = e.clientX - rect.left
      ratio = rect.width > 0 ? (x / rect.width) * 100 : 50
    } else {
      const y = e.clientY - rect.top
      ratio = rect.height > 0 ? (y / rect.height) * 100 : 50
    }

    // Enforce minimum sizes
    const [state0, state1] = this.paneStates
    const min0 = state0.config.minSize ?? 0
    const min1 = state1.config.minSize ?? 0
    const totalPx = orientation === 'horizontal' ? rect.width : rect.height

    const minPercent0 = totalPx > 0 ? (min0 / totalPx) * 100 : 0
    const minPercent1 = totalPx > 0 ? (min1 / totalPx) * 100 : 0

    let newFirst = Math.max(minPercent0, Math.min(100 - minPercent1, ratio))
    let newSecond = 100 - newFirst

    if (newSecond < minPercent1) {
      newSecond = minPercent1
      newFirst = 100 - newSecond
    }

    state0.sizePercent = newFirst
    state1.sizePercent = newSecond

    this.applySizes()
  }

  /** Handle mouse up to end gutter drag */
  private onMouseUp(): void {
    this.isDragging = false
    document.removeEventListener('mousemove', this.handleMouseMove)
    document.removeEventListener('mouseup', this.handleMouseUp)
  }

  /** Find a pane state by id */
  private findPaneState(id: string): PaneState | null {
    for (const state of this.paneStates) {
      if (state.config.id === id) {
        return state
      }
    }
    return null
  }

  /** Get the other pane state (opposite of the given id) */
  private getOtherPane(id: string): PaneState | null {
    if (this.paneStates[0].config.id === id) {
      return this.paneStates[1]
    }
    if (this.paneStates[1].config.id === id) {
      return this.paneStates[0]
    }
    return null
  }
}
