import { Model } from '../core/Model.js'
import { exponentialBackoffDelay, type RetryOptions, shouldRetryApiError, retry } from './retry_logic.js'
import { fromError } from '../exceptions/builder.js'
import { LiveData } from '../reactive/LiveData.js'
import type SdkService from '../services/SdkService.js'
import { filesSortFunction } from '../lib/files/Utils.js'
import type { IFile } from '@api-client/core/models/store/File.js'
import type { ListFileKind } from '@api-client/core/sdk/FilesSdk.js'
import type { ContextListOptions, ContextListResult } from '@api-client/core/events/BaseEvents.js'

export type FilesSource = 'own' | 'shared'

enum ModelState {
  /**
   * Not doing anything.
   */
  idle,
  /**
   * Currently querying for file data.
   */
  reading,
  /**
   * Data was received and the result yielded no results or less than the limit.
   */
  ended,
}

export default class FilesModel extends Model<LiveData<IFile[]>> {
  source: FilesSource = 'own'
  /**
   * The pagination cursor for files to use with the next query.
   */
  cursor?: string

  /**
   * The current state of the model.
   */
  state: ModelState = ModelState.idle

  /**
   * The list of file kinds to list.
   * Folders are always included.
   */
  kinds?: ListFileKind[]

  /**
   * The variable parent folder key.
   * This is set when interacting with the UI or when the `folder` attribute change.
   */
  parent?: string

  override get value(): LiveData<IFile[]> {
    return this.raw as LiveData<IFile[]>
  }

  constructor(
    public oid: string,
    public sdk: SdkService,
    source: FilesSource = 'own'
  ) {
    super()
    this.source = source
    this.raw = new LiveData<IFile[]>([])
  }

  resetListState(): void {
    this.value.set([])
    this.cursor = undefined
    this.state = ModelState.idle
  }

  scheduleQuery(): void {
    this.debounce(this.#query)
  }

  /**
   * Sets a parent and makes the query.
   * It does nothing if the parent is already set.
   *
   * @param key The key of the parent to query. Empty for root folder.
   */
  selectFolder(key?: string): void {
    if (this.parent === key) {
      return
    }
    this.parent = key
    this.resetListState()
    this.scheduleQuery()
  }

  /**
   * Finds a file by the key.
   * @param key The key of the file to find
   * @returns The file, if any
   */
  getFile(key: string): IFile | undefined {
    return this.value.value.find((i) => i.key === key)
  }

  replaceFile(file: IFile): void {
    const files = this.value.value
    const index = files.findIndex((i) => i.key === file.key)
    if (index === -1) {
      files.push(file)
    } else {
      files[index] = file
    }
    files.sort(filesSortFunction)
  }

  addFile(file: IFile): void {
    const files = this.value.value
    files.push(file)
    files.sort(filesSortFunction)
  }

  removeFile(key: string): void {
    const files = this.value.value
    const index = files.findIndex((i) => i.key === key)
    if (index === -1) {
      return
    }
    files.splice(index, 1)
  }

  removeFiles(keys: string[]): void {
    this.value.set(this.value.value.filter((file) => !keys.includes(file.key)))
  }

  async #query(): Promise<void> {
    if (this.state !== ModelState.idle) {
      return
    }
    this.state = ModelState.reading
    try {
      const result = await this.listFiles()
      this.#processFileResults(result)
    } catch (e) {
      const err = fromError(e as Error)
      this.notifyError(new Error(`Unable to load files list. ${err.message}`))
      this.state = ModelState.idle
    }
    this.dispatchEvent(new Event('querycomplete'))
  }

  #processFileResults(result: ContextListResult<IFile>): void {
    const { items, cursor } = result
    if (cursor) {
      this.cursor = cursor
    }
    if (!items.length) {
      this.state = ModelState.ended
      return
    }
    if (!this.value.value.length) {
      // we only sort the first page as later the user scrolls and sorting would change the order.
      items.sort(filesSortFunction)
    }
    this.value.set(this.value.value.concat(items))
    this.state = ModelState.idle
  }

  async listFiles(): Promise<ContextListResult<IFile>> {
    const operation = async (): Promise<ContextListResult<IFile>> => {
      const opts = this.getPageQueryOptions()
      if (this.source === 'shared' && !this.parent) {
        return this.sdk.shared.list(this.oid, opts)
      }
      return this.sdk.file.list(this.oid, opts)
    }
    const retryOptions: RetryOptions = {
      retries: this.retryCount,
      delayMs: exponentialBackoffDelay,
      shouldRetry: shouldRetryApiError,
    }
    return retry(operation, retryOptions)
  }

  getPageQueryOptions(): ContextListOptions {
    const { cursor: filesCursor, parent, limit, kinds } = this
    const opts: ContextListOptions = {
      // descending: true,
    }
    if (filesCursor) {
      opts.cursor = filesCursor
    }
    if (parent) {
      opts.parent = parent
    }
    if (typeof limit === 'number' && limit) {
      opts.limit = limit
    }
    if (Array.isArray(kinds) && kinds.length) {
      opts.filter = {
        op: 'OR',
        filters: kinds.map((kind) => ({
          field: 'kind',
          operator: 'eq',
          value: kind,
        })),
      }
    }
    return opts
  }

  async deleteFiles(keys: string[]): Promise<void> {
    if (!keys.length) {
      return
    }
    const operation = async (): Promise<void> => {
      await this.sdk.file.deleteBulk(this.oid, keys)
    }
    const retryOptions: RetryOptions = {
      delayMs: exponentialBackoffDelay,
      shouldRetry: shouldRetryApiError,
      retries: this.retryCount,
    }
    try {
      await retry(operation, retryOptions)
      this.removeFiles(keys)
    } catch (e) {
      const err = fromError(e as Error)
      this.notifyError(err)
    }
  }
}
