import { html, LitElement, nothing, type TemplateResult } from 'lit'
import app from '../store/app.ts'
import {
  type Bookmark,
  bookmarkDisplayName,
  type BookmarkList,
} from '../store/schema.ts'
import { icon } from '../utils/icon.ts'
import { setMapNav } from '../utils/nav.ts'
import {
  isEditMode,
  LIST_EDIT_MODE_EVENT,
  setEditMode,
} from '../utils/list-edit-mode.ts'

/** Hold duration, in ms, before a press on a row enters Select mode. */
const LONG_PRESS_MS = 500

/**
 * Renders a list of bookmark rows — the shared piece behind both
 * `/bookmarks` (its "not in a list" section) and `/list/{id}` (a list's
 * members), which used to have two independently-written row renderers.
 * Owns the per-row "add to a list" picker plus, in "Select" mode (toggled
 * from either page's header kebab menu via `list-edit-mode.ts`, since header
 * and page are separate router landmarks), a selection bar and bulk
 * favorite/visited/list/delete actions.
 */
export class MBookmarksList extends LitElement {
  static override properties = {
    bookmarks: { attribute: false },
    lists: { attribute: false },
  }

  declare bookmarks: Bookmark[]
  declare lists: BookmarkList[]

  #editMode = false
  #selected = new Set<string>()
  #actionsMenuOpen = false
  #listPickerId: string | null = null
  #bulkListPickerOpen = false
  #pressTimer: ReturnType<typeof setTimeout> | null = null
  #pressFired = false

  constructor() {
    super()
    this.bookmarks = []
    this.lists = []
  }

  protected override createRenderRoot() {
    return this
  }

  #onEditModeChange = () => {
    this.#editMode = isEditMode()
    if (!this.#editMode) {
      this.#selected = new Set()
      this.#actionsMenuOpen = false
    }
    this.requestUpdate()
  }

  // Any click that isn't inside the open actions menu dismisses it — same
  // pattern as the header kebab menus.
  #onDocumentClick = (e: Event) => {
    if (!this.#actionsMenuOpen) return
    const menu = this.querySelector('.bm-select-actions')
    if (menu && e.composedPath().includes(menu)) return
    this.#actionsMenuOpen = false
    this.requestUpdate()
  }

  override connectedCallback() {
    super.connectedCallback()
    this.#editMode = isEditMode()
    globalThis.addEventListener(LIST_EDIT_MODE_EVENT, this.#onEditModeChange)
    document.addEventListener('click', this.#onDocumentClick)
  }

  override disconnectedCallback() {
    super.disconnectedCallback()
    globalThis.removeEventListener(
      LIST_EDIT_MODE_EVENT,
      this.#onEditModeChange,
    )
    document.removeEventListener('click', this.#onDocumentClick)
  }

  #toggleSelected(id: string, checked: boolean) {
    const next = new Set(this.#selected)
    if (checked) next.add(id)
    else next.delete(id)
    this.#selected = next
    this.requestUpdate()
  }

  #toggleSelectAll(checked: boolean) {
    this.#selected = checked
      ? new Set(this.bookmarks.map((b) => b.id))
      : new Set()
    this.requestUpdate()
  }

  #toggleActionsMenu = (e: Event) => {
    e.stopPropagation()
    this.#actionsMenuOpen = !this.#actionsMenuOpen
    this.requestUpdate()
  }

  /** A bulk action is done — leave Select mode entirely rather than just
   * clearing the selection, so the header's menu label flips back too. */
  #afterBulkAction() {
    setEditMode(false)
  }

  #bulkSetFavorite = async (value: boolean) => {
    await Promise.all(
      [...this.#selected].map((id) =>
        app.updateBookmark(id, { favorite: value })
      ),
    )
    this.#afterBulkAction()
  }

  #bulkSetVisited = async (value: boolean) => {
    await Promise.all(
      [...this.#selected].map((id) =>
        app.updateBookmark(id, { visited: value })
      ),
    )
    this.#afterBulkAction()
  }

  #bulkDelete = async () => {
    const n = this.#selected.size
    if (
      !globalThis.confirm(
        `Delete ${n} bookmark${n === 1 ? '' : 's'}? This can't be undone.`,
      )
    ) return
    await Promise.all(
      [...this.#selected].map((id) => app.deleteBookmark(id)),
    )
    this.#afterBulkAction()
  }

  #selectedBookmarks(): Bookmark[] {
    return this.bookmarks.filter((b) => this.#selected.has(b.id))
  }

  #openBulkListPicker = () => {
    this.#actionsMenuOpen = false
    this.#bulkListPickerOpen = true
    this.requestUpdate()
  }

  #closeBulkListPicker = () => {
    this.#bulkListPickerOpen = false
    this.#afterBulkAction()
  }

  #bulkListMembership(listId: string): boolean {
    const sel = this.#selectedBookmarks()
    return sel.length > 0 && sel.every((b) => b.categories.includes(listId))
  }

  #bulkToggleListMembership = async (listId: string, on: boolean) => {
    await Promise.all(
      this.#selectedBookmarks().map((b) => {
        const next = on
          ? [...new Set([...b.categories, listId])]
          : b.categories.filter((c) => c !== listId)
        return app.updateBookmark(b.id, { categories: next })
      }),
    )
    this.requestUpdate()
  }

  #openListPicker(id: string) {
    this.#listPickerId = id
    this.requestUpdate()
  }

  /** Re-read from the store each render; toggling replaces the object. */
  #listPickerBookmark(): Bookmark | undefined {
    if (!this.#listPickerId) return undefined
    return this.bookmarks.find((b) => b.id === this.#listPickerId)
  }

  async #toggleMembership(b: Bookmark, listId: string, on: boolean) {
    const next = on
      ? [...new Set([...b.categories, listId])]
      : b.categories.filter((c) => c !== listId)
    await app.updateBookmark(b.id, { categories: next })
  }

  #navigateTo(b: Bookmark) {
    const [lng, lat] = b.geometry.coordinates
    setMapNav({ lat, lng, zoom: b.zoom, bookmarkId: b.id })
    location.hash = '#!/'
  }

  #enterSelectMode(id: string) {
    this.#pressFired = true
    setEditMode(true)
    this.#toggleSelected(id, true)
  }

  /**
   * Long-pressing a row is a touch shortcut into Select mode with that row
   * already checked — mouse users get the same result instantly via
   * `contextmenu` below (see `#onRowContextMenu`), so this timer only needs
   * to run for touch/pen input.
   */
  #onRowPressStart = (e: PointerEvent, id: string) => {
    if (e.pointerType === 'mouse') return
    this.#pressFired = false
    this.#pressTimer = setTimeout(() => {
      this.#pressTimer = null
      this.#enterSelectMode(id)
    }, LONG_PRESS_MS)
  }

  #onRowPressEnd = () => {
    if (this.#pressTimer === null) return
    clearTimeout(this.#pressTimer)
    this.#pressTimer = null
  }

  /** Right-click is the desktop-equivalent shortcut into Select mode — the
   * same idea as `m-map`'s `contextmenu` handler treating a right-click as
   * an instant version of its own touch long-press. */
  #onRowContextMenu = (e: Event, id: string) => {
    e.preventDefault()
    this.#onRowPressEnd()
    this.#enterSelectMode(id)
  }

  /** Swallows the click that follows a completed long-press so it doesn't
   * also navigate to the bookmark's detail page. */
  #onRowClick = (e: Event) => {
    if (!this.#pressFired) return
    e.preventDefault()
    this.#pressFired = false
  }

  #renderNameLine(b: Bookmark): TemplateResult {
    return html`
      <span class="bm-item-name">
        ${bookmarkDisplayName(b)} ${b.favorite
          ? html`
            <span class="bm-favorite-badge" title="Favorite">${icon(
              'heart',
              '1em',
            )}</span>
          `
          : nothing} ${b.visited
          ? html`
            <span class="bm-visited-badge" title="Visited">${icon(
              'check-circle',
              '1em',
            )}</span>
          `
          : nothing} ${b.images?.length > 0
          ? html`
            <span class="bm-image-badge" title="${b.images
              .length} image${b.images.length === 1 ? '' : 's'}"
            >📷 ${b.images.length}</span>
          `
          : nothing} ${app.isBookmarkShared(b.id)
          ? html`
            <span class="bm-share-badge" title="Shared">${icon(
              'share-2',
              '0.9em',
            )}</span>
          `
          : nothing}
      </span>
    `
  }

  #renderRow(b: Bookmark): TemplateResult {
    const [lng, lat] = b.geometry.coordinates
    const checked = this.#selected.has(b.id)
    const coords = html`
      <span class="bm-item-coords">${lat.toFixed(5)}, ${lng.toFixed(5)}</span>
    `

    return html`
      <div class="bm-item">
        ${this.#editMode
          ? html`
            <input
              id="bm-sel-${b.id}"
              type="checkbox"
              class="bm-item-checkbox"
              aria-label="Select ${bookmarkDisplayName(b)}"
              .checked="${checked}"
              @change="${(e: Event) =>
                this.#toggleSelected(
                  b.id,
                  (e.target as HTMLInputElement).checked,
                )}"
            >
          `
          : nothing}
        ${this.#editMode
          ? html`
            <label class="bm-item-info" for="bm-sel-${b.id}">
              ${this.#renderNameLine(b)} ${coords}
            </label>
          `
          : html`
            <a
              class="bm-item-info"
              href="#!/bookmark/${b.id}"
              @pointerdown="${(e: PointerEvent) =>
                this.#onRowPressStart(e, b.id)}"
              @pointerup="${this.#onRowPressEnd}"
              @pointercancel="${this.#onRowPressEnd}"
              @pointerleave="${this.#onRowPressEnd}"
              @contextmenu="${(e: Event) => this.#onRowContextMenu(e, b.id)}"
              @click="${this.#onRowClick}"
            >
              ${this.#renderNameLine(b)} ${coords}
            </a>
          `}
        ${this.#editMode ? nothing : html`
          <div class="bm-item-actions">
            <button
              class="bm-icon-btn"
              aria-label="Add to a list"
              title="Add to a list"
              @click="${() => this.#openListPicker(b.id)}"
            >
              ${icon('tag')}
            </button>
            <button
              class="bm-icon-btn"
              aria-label="Go to location"
              title="Go to location"
              @click="${() => this.#navigateTo(b)}"
            >
              ${icon('navigation')}
            </button>
          </div>
        `}
      </div>
    `
  }

  #renderSelectBar(): TemplateResult | typeof nothing {
    if (!this.#editMode) return nothing
    const total = this.bookmarks.length
    const selectedCount = this.#selected.size
    const allSelected = total > 0 && selectedCount === total

    return html`
      <div class="bm-select-bar">
        <label class="bm-select-all">
          <input
            type="checkbox"
            .checked="${allSelected}"
            ?disabled="${total === 0}"
            @change="${(e: Event) =>
              this.#toggleSelectAll((e.target as HTMLInputElement).checked)}"
          >
          <span>${selectedCount > 0
            ? `${selectedCount} selected`
            : 'Select all'}</span>
        </label>
        <div class="bm-select-actions">
          <button
            type="button"
            class="bm-select-actions-btn"
            ?disabled="${selectedCount === 0}"
            @click="${this.#toggleActionsMenu}"
          >
            Actions ${icon('chevron-down', '16px')}
          </button>
          ${this.#actionsMenuOpen
            ? html`
              <div class="header-menu-panel" role="menu">
                <button
                  class="header-menu-item"
                  role="menuitem"
                  @click="${() => this.#bulkSetFavorite(true)}"
                >
                  ${icon('heart', '18px')}
                  <span>Mark favorite</span>
                </button>
                <button
                  class="header-menu-item"
                  role="menuitem"
                  @click="${() => this.#bulkSetFavorite(false)}"
                >
                  ${icon('heart', '18px')}
                  <span>Remove favorite</span>
                </button>
                <button
                  class="header-menu-item"
                  role="menuitem"
                  @click="${() => this.#bulkSetVisited(true)}"
                >
                  ${icon('check-circle', '18px')}
                  <span>Mark visited</span>
                </button>
                <button
                  class="header-menu-item"
                  role="menuitem"
                  @click="${() => this.#bulkSetVisited(false)}"
                >
                  ${icon('circle', '18px')}
                  <span>Mark unvisited</span>
                </button>
                <button
                  class="header-menu-item"
                  role="menuitem"
                  @click="${this.#openBulkListPicker}"
                >
                  ${icon('tag', '18px')}
                  <span>Add to lists…</span>
                </button>
                <button
                  class="header-menu-item header-menu-item--danger"
                  role="menuitem"
                  @click="${this.#bulkDelete}"
                >
                  ${icon('x', '18px')}
                  <span>Delete</span>
                </button>
              </div>
            `
            : nothing}
        </div>
      </div>
    `
  }

  #renderSingleListPickerDialog(): TemplateResult {
    const b = this.#listPickerBookmark()
    return html`
      <ui-dialog
        ?open="${this.#listPickerId !== null}"
        @dismiss="${() => {
          this.#listPickerId = null
          this.requestUpdate()
        }}"
      >
        <dialog>
          <article class="bm-dialog">
            <h2 class="bm-dialog-title">Add to lists</h2>
            ${this.lists.length === 0
              ? html`
                <p class="bm-empty">No lists yet — create one first.</p>
              `
              : this.lists.map((l) => {
                const member = b?.categories.includes(l.id) ?? false
                return html`
                  <label class="bookmark-page-list-option">
                    <input
                      type="checkbox"
                      .checked="${member}"
                      @change="${(e: Event) =>
                        b && this.#toggleMembership(
                          b,
                          l.id,
                          (e.target as HTMLInputElement).checked,
                        )}"
                    >
                    <span>${l.name}</span>
                  </label>
                `
              })}
            <div class="bm-form-actions">
              <button
                type="button"
                @click="${() => {
                  this.#listPickerId = null
                  this.requestUpdate()
                }}"
              >
                Done
              </button>
            </div>
          </article>
        </dialog>
      </ui-dialog>
    `
  }

  #renderBulkListPickerDialog(): TemplateResult {
    return html`
      <ui-dialog
        ?open="${this.#bulkListPickerOpen}"
        @dismiss="${this.#closeBulkListPicker}"
      >
        <dialog>
          <article class="bm-dialog">
            <h2 class="bm-dialog-title">
              Add ${this.#selected.size} bookmark${this.#selected.size === 1
                ? ''
                : 's'} to lists
            </h2>
            ${this.lists.length === 0
              ? html`
                <p class="bm-empty">No lists yet — create one first.</p>
              `
              : this.lists.map((l) => {
                const member = this.#bulkListMembership(l.id)
                return html`
                  <label class="bookmark-page-list-option">
                    <input
                      type="checkbox"
                      .checked="${member}"
                      @change="${(e: Event) =>
                        this.#bulkToggleListMembership(
                          l.id,
                          (e.target as HTMLInputElement).checked,
                        )}"
                    >
                    <span>${l.name}</span>
                  </label>
                `
              })}
            <div class="bm-form-actions">
              <button type="button" @click="${this.#closeBulkListPicker}">
                Done
              </button>
            </div>
          </article>
        </dialog>
      </ui-dialog>
    `
  }

  override render(): TemplateResult {
    return html`
      ${this.#renderSelectBar()}
      ${this.bookmarks.map((b) => this.#renderRow(b))}
      ${this.#renderSingleListPickerDialog()}
      ${this.#renderBulkListPickerDialog()}
    `
  }
}

customElements.define('m-bookmarks-list', MBookmarksList)
