// Note: This file is owned by the cloud delivered
// `studio` bundle. It is downloaded and copied to the app.
// It should not be modified directly in the app.

// Recording state: 'recording' (active), 'paused' (user-controlled, resumable),
// or 'disabled' (system-controlled, cannot be activated until conditions change).
export type RecordingState = 'recording' | 'paused' | 'disabled'

export type SynchronizationMetadata = {
  timestamp: number
  sequence: number
  frameId: string
}

export type CloudStatus =
  | 'isLoggedOut'
  | 'needsOrgConnect'
  | 'needsProjectConnect'
  | 'needsRecordedRun'
  | 'allTasksCompleted'

export interface LoginUserData {
  id: string
  fullName: string | null
  email: string | null
}

export interface OrganizationData {
  id: string
  cypressAiDisabled?: boolean | null
}

export interface UserProjectStatusStore {
  user: {
    isLoggedIn: boolean
  }
  project: {
    isProjectConnected: boolean
    isNotAuthorized: boolean
  }
  openLoginConnectModal: (options: { utmMedium: string }) => void
  cloudStatus: CloudStatus
  projectId: string
  userData?: LoginUserData
  organization?: OrganizationData
}

export interface AutSnapshotStore {
  isSnapshotPinned: boolean
}

export interface RequestProjectAccessMutationResult {
  data?: {
    cloudProjectRequestAccess: {
      hasRequestedAccess: boolean
    }
  }
  error?: any
}

export interface RequestProjectAccessMutation {
  executeMutation: (variables: {
    projectId: string
  }) => Promise<RequestProjectAccessMutationResult>
}

const SPEC_DIRTY_DATA_MODULES = Object.freeze({
  STUDIO: {
    name: 'Studio',
  },
})

export type SpecDirtyDataModule =
  (typeof SPEC_DIRTY_DATA_MODULES)[keyof typeof SPEC_DIRTY_DATA_MODULES]

export type SpecDirtyDataModuleKey = keyof typeof SPEC_DIRTY_DATA_MODULES

export interface SpecDirtyDataStore {
  setDirtyStateForKey: (key: SpecDirtyDataModuleKey, isDirty: boolean) => void
  getDirtyStateForKey: (key: SpecDirtyDataModuleKey) => boolean
  getDirtyModules: () => SpecDirtyDataModule[]
  isDirty: () => boolean
  resetDirtyState: () => void
}

export interface StudioPanelProps {
  canAccessStudioAI: boolean
  onStudioPanelClose?: () => void
  studioSessionId?: string
  useRunnerStatus?: RunnerStatusShape
  useTestContentRetriever?: TestContentRetrieverShape
  useCypress?: CypressShape
  useSnapshotIframe?: SnapshotIframeShape
  autUrlSelector?: string
  studioAiAvailable?: boolean
  userProjectStatusStore?: UserProjectStatusStore
  hasRequestedProjectAccess?: boolean
  requestProjectAccessMutation?: RequestProjectAccessMutation
  specDirtyDataStore?: SpecDirtyDataStore
  isSelectorPlaygroundOpen?: boolean
  // Callback to close the Selector Playground
  onCloseSelectorPlayground?: () => void
  autSnapshotStore?: AutSnapshotStore
  // When non-null, navigation was blocked due to unsaved changes.
  // Call this function to discard changes and resume the navigation.
  pendingNavigationResume?: (() => void) | null
}

export type StudioPanelShape = (props: StudioPanelProps) => JSX.Element

export interface StudioAppDefaultShape {
  // Purposefully do not use React in this signature to avoid conflicts when this type gets
  // transferred to the Cypress app
  StudioPanel: StudioPanelShape
}

export type CypressInternal = Cypress.Cypress &
CyEventEmitter & {
  state<V = any>(key: string): V
  state<V = any>(key: string, value: any): V
  // The main AUT iframe
  $autIframe: JQuery<HTMLIFrameElement>
  // The container for the AUT panel, which contains the frames and snapshots controls
  $autPanelContainer?: JQuery<HTMLElement>
  // The container for the AUT panel iframes, which contains the AUT iframe and snapshot iframes
  $autIframesContainer?: JQuery<HTMLElement>
  // The individual snapshot iframes rendered by Cypress for studio snapshotrendering
  $autSnapshotIframes?: JQuery<HTMLIFrameElement>[]
  mocha: {
    getRootSuite: () => Suite
    getRunner: () => {
      stats: {
        suites: number
        tests: number
      }
    }
  }
  areSourceMapsAvailable?: boolean
  stackUtils?: {
    getSourceDetailsForFirstLine: (
      stack: string,
      projectRoot: string
    ) => {
      line: number
      column: number
      file: string
    }
  }
  // External typings do not expose the `getSelectorPriority` function on the ElementSelector object
  ElementSelector: {
    defaults(options: Partial<Cypress.ElementSelectorDefaultsOptions>): void
    getSelectorPriority?: () => Cypress.SelectorPriority[]
  }
}

export type LocalRecommendationId = string

export interface PreSettledRecommendation {
  // client-side generated id, same as the counterpart pending recommendation
  id: LocalRecommendationId
  // server-side generated id
  generationId: string
  // the assertions generated by the LLM (may be updated if user edits within the recommendation)
  generatedAssertions: string
  // the original assertions generated by the LLM (preserved for analytics)
  originalGeneratedAssertions: string
  // synchronization metadata for each snapshot used to generate this recommendation
  snapshotSynchronizationMetadata: SynchronizationMetadata[]
  // the reason(s) for the recommendation, from the LLM response
  reason?: string[]
  // the node ID of the target element for this recommendation (for highlighting)
  nodeId?: number
}

// a settled recommendation with the assertions generated by the LLM
export interface SettledRecommendation extends PreSettledRecommendation {
  // line that the recommendation starts on
  startingLineNumber: number
}

export type SettledRecommendationsById = Record<
  LocalRecommendationId,
  SettledRecommendation
>

export interface TestBlock {
  content: string
  testBodyPosition: {
    contentStart: number
    contentEnd: number
    indentation: string
    indentationType?: IndentationType
  }
  recommendations?: SettledRecommendationsById
}

export type RunnerStatus = 'running' | 'finished'

export interface RunnerStatusProps {
  Cypress: CypressInternal
}

export interface CypressProps {
  Cypress: CypressInternal
}

export type CypressShape = (props: CypressProps) => {
  currentCypress: CypressInternal
}

export type RunnerStatusShape = (props: RunnerStatusProps) => {
  runnerStatus: RunnerStatus
}

export type SnapshotIframeShape = (props: CypressProps) => void

export interface TestContentRetrieverProps {
  Cypress: CypressInternal
  showCypressGrepError: boolean
  isCucumberSpec: boolean
}

export type TestContentRetrieverShape = (props: TestContentRetrieverProps) => {
  isLoading: boolean
  testBlock: TestBlock | null
  isCreatingNewTest: boolean
}

export type Suite = {
  id: string
  title: string
  suites?: Suite[]
  invocationDetails: {
    line: number
    column: number
  }
}

export type IndentationType = 'space' | 'tab'

// copied from the Cypress App
export interface AutSnapshot {
  id?: number
  name?: string
  $el: any
  snapshot?: AutSnapshot
  coords: [number, number]
  scrollBy: {
    x: number
    y: number
  }
  snapshots: AutSnapshot[]
  highlightAttr: string
  htmlAttrs: Record<string, any> // Type is NamedNodeMap, not sure if we should include lib: ["DOM"]
  viewportHeight: number
  viewportWidth: number
  url: string
  body: {
    get: () => unknown // TODO: find out what this is, some sort of JQuery API.
  }
}
