import { Model } from '../core/Model.js'
import { bound } from '../decorators/bound.js'
import { DomainEntityKind, DomainModelKind, DomainNamespaceKind } from '@api-client/core/models/kinds.js'
import type {
  DomainBrowseRecentItem,
  DomainBrowseRecentItems,
  DomainBrowserPickItem,
  DomainRenameEventDetail,
} from '../modeling/types.js'
import type ConfigurationService from '../services/ConfigurationService.js'

export const RECENT_ITEMS_KEY = 'com.api-client.schema.recent.items'
export const RECENT_ITEMS_MAX = 10

/**
 * Model for browsing domain entities, models, and namespaces.
 * Has the logic related to storing "recently browsed items" in the local storage.
 */
export class DomainBrowserModel extends Model<string[]> {
  /**
   * The key of the domain that is currently being browsed.
   * This is used to filter the recent items to only those that belong to the current domain.
   * If the domain is not set, it means that the recent items are not filtered by the domain.
   */
  public domainKey?: string

  /**
   * The key of the recent items list that is used to store the recent items in the local storage.
   */
  get key(): string {
    const { domainKey } = this
    if (!domainKey) {
      throw new Error('Domain key is not set. Please set the domain key before using the model.')
    }
    return `${RECENT_ITEMS_KEY}.${domainKey}`
  }

  config: ConfigurationService

  constructor(config: ConfigurationService) {
    super()
    this.config = config
  }

  @bound
  handleEntityRename(e: Event): void {
    const event = e as CustomEvent<DomainRenameEventDetail>
    const { key, label, namespace, kind } = event.detail
    // Update the recent items when an entity is renamed
    this.updateRecentItem(key, label, namespace, kind)
  }

  async read(): Promise<DomainBrowseRecentItems> {
    if (!this.domainKey) {
      return { items: [], updated: Date.now() }
    }
    const recent = (await this.config.local.get(this.key)) as DomainBrowseRecentItems | undefined
    if (!recent) {
      return { items: [], updated: Date.now() }
    }
    return recent
  }

  filterRecentItems(items: DomainBrowseRecentItem[], allowed: DomainBrowserPickItem[]): DomainBrowseRecentItem[] {
    if (allowed.length) {
      return items.filter((item) => {
        if (item.kind === DomainEntityKind && allowed.includes(DomainEntityKind)) {
          return true
        }
        if (item.kind === DomainModelKind && allowed.includes(DomainModelKind)) {
          return true
        }
        if (item.kind === DomainNamespaceKind && allowed.includes(DomainNamespaceKind)) {
          return true
        }
        return false
      })
    }
    return items
  }

  sort(a: DomainBrowseRecentItem, b: DomainBrowseRecentItem): number {
    return b.used - a.used
  }

  async addRecentItem(item: DomainBrowseRecentItem): Promise<DomainBrowseRecentItems> {
    const recent = await this.read()
    if (!recent) {
      const newRecent: DomainBrowseRecentItems = {
        items: [item],
        updated: Date.now(),
      }
      await this.config.local.set(this.key, newRecent)
      return newRecent
    }
    const existingIndex = recent.items.findIndex((i) => i.key === item.key)
    if (existingIndex !== -1) {
      recent.items.splice(existingIndex, 1)
    }
    recent.items.unshift(item)
    if (recent.items.length > RECENT_ITEMS_MAX) {
      recent.items.pop()
    }
    recent.updated = Date.now()
    await this.config.local.set(this.key, recent)
    return recent
  }

  async updateRecentItem(key: string, label: string, domain: string, kind: string): Promise<void> {
    const recent = await this.read()
    const item = recent.items.find((i) => i.key === key && i.domain === domain && i.kind === kind)
    if (!item) {
      return
    }
    item.label = label
    await this.config.local.set(this.key, recent)
  }
}
