import {getNamelessWorkspaceIdentifier, getWorkspaceIdentifier} from './helpers'
import {type WorkspaceLike} from './types'
import {WorkspaceValidationError} from './WorkspaceValidationError'

/** @internal */
export interface ValidateWorkspaceOptions {
  workspaces: WorkspaceLike[]
}

/**
 * Validates workspace configuration, throwing if:
 *
 * - Workspaces do not all have base paths and names (if multiple given)
 * - Base paths or names are invalid
 * - Base paths or names are not unique
 *
 * @internal
 */
export function validateWorkspaces({workspaces}: ValidateWorkspaceOptions): void {
  if (workspaces.length === 0) {
    throw new WorkspaceValidationError('At least one workspace is required.')
  }

  validateNames(workspaces)
  validateBasePaths(workspaces)
}

/**
 * Validates the workspace names of every workspace
 * Only exported for testing purposes
 *
 * @param workspaces - An array of workspaces
 * @internal
 */
export function validateNames(workspaces: WorkspaceLike[]): void {
  const isSingleWorkspace = workspaces.length === 1
  const names = new Map<string, {index: number; workspace: WorkspaceLike}>()
  workspaces.forEach((workspace, index) => {
    const {name: rawName, title} = workspace
    const thisIdentifier = getNamelessWorkspaceIdentifier(title, index)

    if (!rawName && !isSingleWorkspace) {
      throw new WorkspaceValidationError(
        'All workspaces must have a `name`, unless only a single workspace is defined. ' +
          `Workspace ${thisIdentifier} did not define a \`name\`.`,
        {workspace, index},
      )
    }

    const name = isSingleWorkspace && typeof rawName === 'undefined' ? 'default' : rawName

    if (typeof name !== 'string') {
      throw new WorkspaceValidationError(
        `Workspace at index ${index} defined an invalid \`name\` - must be a string.`,
        {workspace, index},
      )
    }

    const normalized = name.toLowerCase()
    const existingWorkspace = names.get(normalized)

    if (existingWorkspace) {
      const prevIdentifier = getNamelessWorkspaceIdentifier(
        existingWorkspace.workspace.title,
        existingWorkspace.index,
      )
      throw new WorkspaceValidationError(
        `\`name\`s must be unique. Workspace ${prevIdentifier} and ` +
          `workspace ${thisIdentifier} both have the \`name\` \`${name}\``,
        {workspace, index},
      )
    }

    names.set(normalized, {index, workspace})

    if (!/^[a-z0-9][a-z0-9_-]*$/i.test(name)) {
      throw new WorkspaceValidationError(
        `All workspace \`name\`s must consist of only a-z, 0-9, underscore and dashes, ` +
          `and cannot begin with an underscore or dash. ` +
          `Workspace ${thisIdentifier} has the invalid name \`${name}\``,
        {workspace, index},
      )
    }
  })
}

/**
 * Validates the base paths of every workspace
 * Only exported for testing purposes
 *
 * @param workspaces - An array of workspaces
 * @internal
 */
export function validateBasePaths(workspaces: WorkspaceLike[]): void {
  // If we have more than a single workspace, every workspace needs a basepath
  if (workspaces.length > 1) {
    workspaces.every(hasBasePath) // Throws on missing basePath
  }

  workspaces.every(validateBasePath)

  const [firstWorkspace, ...restOfWorkspaces] = workspaces
  const firstWorkspaceSegmentCount = (firstWorkspace.basePath || '/')
    // remove starting slash before splitting
    .substring(1)
    .split('/')
    .filter(Boolean).length

  restOfWorkspaces.forEach((workspace, index) => {
    const workspaceSegmentCount = (workspace.basePath || '/')
      // remove starting slash before splitting
      .substring(1)
      .split('/').length

    if (firstWorkspaceSegmentCount !== workspaceSegmentCount) {
      throw new WorkspaceValidationError(
        `All workspace \`basePath\`s must have the same amount of segments. Workspace \`${getWorkspaceIdentifier(
          firstWorkspace,
          index,
        )}\` had ${firstWorkspaceSegmentCount} segment${
          firstWorkspaceSegmentCount === 1 ? '' : 's'
        } \`${firstWorkspace.basePath}\` but workspace \`${getWorkspaceIdentifier(
          workspace,
          index,
        )}\` had ${workspaceSegmentCount} segment${workspaceSegmentCount === 1 ? '' : 's'} \`${
          workspace.basePath
        }\``,
        {workspace, index},
      )
    }
  })

  const basePaths = new Map<string, string>()
  workspaces.forEach((workspace, index) => {
    const basePath = (workspace.basePath || '').toLowerCase()

    const existingWorkspace = basePaths.get(basePath)
    if (existingWorkspace) {
      throw new WorkspaceValidationError(
        `\`basePath\`s must be unique. Workspaces \`${existingWorkspace}\` and ` +
          `\`${getWorkspaceIdentifier(
            workspace,
            index,
          )}\` both have the \`basePath\` \`${basePath}\``,
        {workspace, index},
      )
    }

    basePaths.set(basePath, getWorkspaceIdentifier(workspace, index))
  })
}

function hasBasePath(workspace: WorkspaceLike, index: number) {
  const {name, basePath} = workspace
  if (basePath && typeof basePath === 'string') {
    return true
  }

  if (typeof basePath === 'undefined') {
    throw new WorkspaceValidationError(
      `If more than one workspace is defined, every workspace must have a \`basePath\` defined. ` +
        `Workspace \`${name}\` is missing a \`basePath\``,
      {workspace, index},
    )
  }

  throw new WorkspaceValidationError(
    `If more than one workspace is defined, every workspace must have a \`basePath\` defined. ` +
      `Workspace \`${name}\` has an invalid \`basePath\` (must be a non-empty string)`,
    {workspace, index},
  )
}

function validateBasePath(workspace: WorkspaceLike, index: number) {
  const {name, basePath} = workspace

  // Empty base paths are okay (we're validating uniqueness and presence on more
  // than a single workspace in `validateBasePaths`)
  if (!basePath || basePath === '/') {
    return
  }

  if (!/^\/[a-z0-9/_-]*[a-z0-9_-]+$/i.test(basePath)) {
    throw new WorkspaceValidationError(
      `All workspace \`basePath\`s must start with a leading \`/\`, ` +
        `consist of only URL safe characters, ` +
        `and cannot end with a trailing \`/\`. ` +
        `Workspace \`${name}\`'s basePath is \`${basePath}\``,
      {workspace, index},
    )
  }
}
