import type { HybridObjectSpec } from '../HybridObjectSpec.js'
import { SwiftCxxBridgedType } from './SwiftCxxBridgedType.js'
import type { Property } from '../Property.js'
import { indent } from '../../utils.js'
import type { Method } from '../Method.js'
import {
  createFileMetadataString,
  escapeCppName,
  isNotDuplicate,
} from '../helpers.js'
import type { SourceFile } from '../SourceFile.js'
import { getHybridObjectName } from '../getHybridObjectName.js'
import { getForwardDeclaration } from '../c++/getForwardDeclaration.js'
import { NitroConfig } from '../../config/NitroConfig.js'
import { includeHeader } from '../c++/includeNitroHeader.js'
import { getUmbrellaHeaderName } from '../../autolinking/ios/createSwiftUmbrellaHeader.js'
import { HybridObjectType } from '../types/HybridObjectType.js'
import { addKnownType } from '../createType.js'
import { ResultWrappingType } from '../types/ResultWrappingType.js'

export function getBridgeNamespace() {
  return NitroConfig.getCxxNamespace('swift', 'bridge', 'swift')
}

/**
 * Creates a Swift class that bridges Swift over to C++.
 * We need this because not all Swift types are accessible in C++, and vice versa.
 *
 * For example, Enums need to be converted to Int32 (because of a Swift compiler bug),
 * Promise<..> has to be converted to a Promise<..>, exceptions have to be handled
 * via custom Result types, etc..
 */
export function createSwiftHybridObjectCxxBridge(
  spec: HybridObjectSpec
): SourceFile[] {
  const name = getHybridObjectName(spec.name)
  const moduleName = NitroConfig.getIosModuleName()

  const propertiesBridge = spec.properties.map((p) =>
    getPropertyForwardImplementation(p)
  )

  const methodsBridge = spec.methods.map((m) =>
    getMethodForwardImplementation(m)
  )

  const baseClasses = spec.baseTypes.map((base) => {
    const baseName = getHybridObjectName(base.name)
    return baseName.HybridTSpecCxx
  })
  const hasBase = baseClasses.length > 0

  if (spec.isHybridView && !hasBase) {
    methodsBridge.push(
      `
public final func getView() -> UnsafeMutableRawPointer {
  return Unmanaged.passRetained(__implementation.view).toOpaque()
}
`.trim(),
      `
public final func beforeUpdate() {
  __implementation.beforeUpdate()
}
  `.trim(),
      `
public final func afterUpdate() {
  __implementation.afterUpdate()
}
`.trim()
    )
  }

  const hybridObject = new HybridObjectType(spec)
  const bridgedType = new SwiftCxxBridgedType(hybridObject)
  const bridge = bridgedType.getRequiredBridge()
  if (bridge == null) throw new Error(`HybridObject Type should have a bridge!`)

  const weakifyBridge = bridge.dependencies.find((d) =>
    d.funcName.startsWith('weakify')
  )
  if (weakifyBridge == null)
    throw new Error(
      `HybridObject ${spec.name} does not have a weakify_..() bridge!`
    )

  const cppWeakPtrName = escapeCppName(hybridObject.getCode('c++', 'weak'))

  const baseGetCxxPartOverrides = spec.baseTypes.map((base) => {
    const baseHybridObject = new HybridObjectType(base)
    const bridgedBase = new SwiftCxxBridgedType(baseHybridObject)
    const baseBridge = bridgedBase.getRequiredBridge()
    if (baseBridge == null)
      throw new Error(`HybridObject ${base.name}'s bridge cannot be null!`)

    const upcastBridge = bridge.dependencies.find(
      (b) =>
        b.funcName.includes(escapeCppName(spec.name)) &&
        b.funcName.includes(escapeCppName(base.name))
    )
    if (upcastBridge == null)
      throw new Error(
        `HybridObject ${spec.name}'s upcast-bridge cannot be found! ${JSON.stringify(baseBridge)}`
      )

    return `
public override func getCxxPart() -> bridge.${baseBridge.specializationName} {
  let ownCxxPart: bridge.${bridge.specializationName} = getCxxPart()
  return bridge.${upcastBridge.funcName}(ownCxxPart)
}`.trim()
  })

  const swiftCxxWrapperCode = `
${createFileMetadataString(`${name.HybridTSpecCxx}.swift`)}

import Foundation
import NitroModules

/**
 * A class implementation that bridges ${name.HybridTSpec} over to C++.
 * In C++, we cannot use Swift protocols - so we need to wrap it in a class to make it strongly defined.
 *
 * Also, some Swift types need to be bridged with special handling:
 * - Enums need to be wrapped in Structs, otherwise they cannot be accessed bi-directionally (Swift bug: https://github.com/swiftlang/swift/issues/75330)
 * - Other HybridObjects need to be wrapped/unwrapped from the Swift TCxx wrapper
 * - Throwing methods need to be wrapped with a Result<T, Error> type, as exceptions cannot be propagated to C++
 */
${hasBase ? `public class ${name.HybridTSpecCxx} : ${baseClasses.join(', ')}` : `public class ${name.HybridTSpecCxx}`} {
  /**
   * The Swift <> C++ bridge's namespace (\`${NitroConfig.getCxxNamespace('c++', 'bridge', 'swift')}\`)
   * from \`${moduleName}-Swift-Cxx-Bridge.hpp\`.
   * This contains specialized C++ templates, and C++ helper functions that can be accessed from Swift.
   */
  public typealias bridge = ${getBridgeNamespace()}

  /**
   * Holds an instance of the \`${name.HybridTSpec}\` Swift protocol.
   */
  private var __implementation: any ${name.HybridTSpec}

  /**
   * Holds a weak pointer to the C++ class that wraps the Swift class.
   */
  private var __cxxPart: bridge.${cppWeakPtrName}

  /**
   * Create a new \`${name.HybridTSpecCxx}\` that wraps the given \`${name.HybridTSpec}\`.
   * All properties and methods bridge to C++ types.
   */
  public init(_ implementation: any ${name.HybridTSpec}) {
    self.__implementation = implementation
    self.__cxxPart = .init()
    ${hasBase ? 'super.init(implementation)' : '/* no base class */'}
  }

  /**
   * Get the actual \`${name.HybridTSpec}\` instance this class wraps.
   */
  @inline(__always)
  public func get${name.HybridTSpec}() -> any ${name.HybridTSpec} {
    return __implementation
  }

  /**
   * Casts this instance to a retained unsafe raw pointer.
   * This acquires one additional strong reference on the object!
   */
  public ${hasBase ? 'override func' : 'func'} toUnsafe() -> UnsafeMutableRawPointer {
    return Unmanaged.passRetained(self).toOpaque()
  }

  /**
   * Casts an unsafe pointer to a \`${name.HybridTSpecCxx}\`.
   * The pointer has to be a retained opaque \`Unmanaged<${name.HybridTSpecCxx}>\`.
   * This removes one strong reference from the object!
   */
  public ${hasBase ? 'override class func' : 'class func'} fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> ${name.HybridTSpecCxx} {
    return Unmanaged<${name.HybridTSpecCxx}>.fromOpaque(pointer).takeRetainedValue()
  }

  /**
   * Gets (or creates) the C++ part of this Hybrid Object.
   * The C++ part is a \`${bridge.cxxType}\`.
   */
  public func getCxxPart() -> bridge.${bridge.specializationName} {
    let cachedCxxPart = self.__cxxPart.lock()
    if cachedCxxPart.__convertToBool() {
      return cachedCxxPart
    } else {
      let newCxxPart = bridge.${bridge.funcName}(self.toUnsafe())
      __cxxPart = bridge.${weakifyBridge.funcName}(newCxxPart)
      return newCxxPart
    }
  }

  ${indent(baseGetCxxPartOverrides.join('\n'), '  ')}

  /**
   * Get the memory size of the Swift class (plus size of any other allocations)
   * so the JS VM can properly track it and garbage-collect the JS object if needed.
   */
  @inline(__always)
  public ${hasBase ? 'override var' : 'var'} memorySize: Int {
    return MemoryHelper.getSizeOf(self.__implementation) + self.__implementation.memorySize
  }

  // Properties
  ${indent(propertiesBridge.join('\n\n'), '  ')}

  // Methods
  ${indent(methodsBridge.join('\n\n'), '  ')}
}
  `

  const cppProperties = spec.properties
    .map((p) => {
      const bridged = new SwiftCxxBridgedType(p.type)
      let getter: string
      let setter: string

      const getterName = p.getGetterName('swift')
      const setterName = p.getSetterName('swift')
      if (bridged.needsSpecialHandling) {
        // we need custom C++ -> Swift conversion code
        getter = `
auto __result = _swiftPart.${getterName}();
return ${bridged.parseFromSwiftToCpp('__result', 'c++')};
`
        setter = `_swiftPart.${setterName}(${bridged.parseFromCppToSwift(p.name, 'c++')});`
      } else {
        // just forward value directly
        getter = `return _swiftPart.${getterName}();`
        setter = `_swiftPart.${setterName}(std::forward<decltype(${p.name})>(${p.name}));`
      }
      return p.getCode(
        'c++',
        { inline: true, override: true, noexcept: true },
        {
          getter: getter.trim(),
          setter: setter.trim(),
        }
      )
    })
    .join('\n')

  const cppMethods = spec.methods
    .map((m) => {
      const params = m.parameters
        .map((p) => {
          const bridged = new SwiftCxxBridgedType(p.type)
          if (bridged.needsSpecialHandling) {
            // we need custom C++ -> Swift conversion code
            return bridged.parseFromCppToSwift(p.name, 'c++')
          } else {
            // just forward value directly
            return `std::forward<decltype(${p.name})>(${p.name})`
          }
        })
        .join(', ')
      const bridgedReturnType = new SwiftCxxBridgedType(m.returnType, true)
      const hasResult = m.returnType.kind !== 'void'
      let body: string
      if (hasResult) {
        // func returns something
        body = `
auto __result = _swiftPart.${m.name}(${params});
if (__result.hasError()) [[unlikely]] {
  std::rethrow_exception(__result.error());
}
auto __value = std::move(__result.value());
return ${bridgedReturnType.parseFromSwiftToCpp('__value', 'c++')};
        `.trim()
      } else {
        // void func
        body = `
auto __result = _swiftPart.${m.name}(${params});
if (__result.hasError()) [[unlikely]] {
  std::rethrow_exception(__result.error());
}
        `.trim()
      }

      return m.getCode('c++', { inline: true, override: true }, body)
    })
    .join('\n')

  const allBridgedTypes = [
    ...spec.properties.flatMap((p) => new SwiftCxxBridgedType(p.type)),
    ...spec.methods.flatMap((m) => {
      const bridgedReturn = new SwiftCxxBridgedType(m.returnType)
      const bridgedParams = m.parameters.map(
        (p) => new SwiftCxxBridgedType(p.type)
      )
      return [bridgedReturn, ...bridgedParams]
    }),
  ]
  const cxxNamespace = NitroConfig.getCxxNamespace('c++')
  const iosModuleName = NitroConfig.getIosModuleName()
  const extraImports = allBridgedTypes.flatMap((b) => b.getRequiredImports())

  const cppBaseClasses = [`public virtual ${name.HybridTSpec}`]
  const cppBaseCtorCalls = [`HybridObject(${name.HybridTSpec}::TAG)`]
  for (const base of spec.baseTypes) {
    const baseName = getHybridObjectName(base.name)
    cppBaseClasses.push(`public virtual ${baseName.HybridTSpecSwift}`)
    cppBaseCtorCalls.push(`${baseName.HybridTSpecSwift}(swiftPart)`)
    extraImports.push({
      language: 'c++',
      name: `${baseName.HybridTSpecSwift}.hpp`,
      space: 'user',
      forwardDeclaration: getForwardDeclaration(
        'class',
        baseName.HybridTSpecSwift,
        cxxNamespace
      ),
    })
  }

  const extraForwardDeclarations = extraImports
    .map((i) => i.forwardDeclaration)
    .filter((v) => v != null)
    .filter(isNotDuplicate)
  const extraIncludes = extraImports
    .map((i) => includeHeader(i))
    .filter(isNotDuplicate)

  // TODO: Remove forward declaration once Swift fixes the wrong order in generated -Swift.h headers!
  const cppHybridObjectCode = `
${createFileMetadataString(`${name.HybridTSpecSwift}.hpp`)}

#pragma once

#include "${name.HybridTSpec}.hpp"

${getForwardDeclaration('class', name.HybridTSpecCxx, iosModuleName)}

${extraForwardDeclarations.join('\n')}

${extraIncludes.join('\n')}

#include "${getUmbrellaHeaderName()}"

namespace ${cxxNamespace} {

  /**
   * The C++ part of ${name.HybridTSpecCxx}.swift.
   *
   * ${name.HybridTSpecSwift} (C++) accesses ${name.HybridTSpecCxx} (Swift), and might
   * contain some additional bridging code for C++ <> Swift interop.
   *
   * Since this obviously introduces an overhead, I hope at some point in
   * the future, ${name.HybridTSpecCxx} can directly inherit from the C++ class ${name.HybridTSpec}
   * to simplify the whole structure and memory management.
   */
  class ${name.HybridTSpecSwift}: ${cppBaseClasses.join(', ')} {
  public:
    // Constructor from a Swift instance
    explicit ${name.HybridTSpecSwift}(const ${iosModuleName}::${name.HybridTSpecCxx}& swiftPart):
      ${indent(cppBaseCtorCalls.join(',\n'), '      ')},
      _swiftPart(swiftPart) { }

  public:
    // Get the Swift part
    inline ${iosModuleName}::${name.HybridTSpecCxx}& getSwiftPart() noexcept {
      return _swiftPart;
    }

  public:
    // Get memory pressure
    inline size_t getExternalMemorySize() noexcept override {
      return _swiftPart.getMemorySize();
    }

  public:
    // Properties
    ${indent(cppProperties, '    ')}

  public:
    // Methods
    ${indent(cppMethods, '    ')}

  private:
    ${iosModuleName}::${name.HybridTSpecCxx} _swiftPart;
  };

} // namespace ${cxxNamespace}
  `
  const cppHybridObjectCodeCpp = `
${createFileMetadataString(`${name.HybridTSpecSwift}.cpp`)}

#include "${name.HybridTSpecSwift}.hpp"

namespace ${cxxNamespace} {
} // namespace ${cxxNamespace}
  `

  const files: SourceFile[] = []
  files.push(...allBridgedTypes.flatMap((b) => b.getExtraFiles()))
  files.push({
    content: swiftCxxWrapperCode,
    language: 'swift',
    name: `${name.HybridTSpecCxx}.swift`,
    subdirectory: [],
    platform: 'ios',
  })
  files.push({
    content: cppHybridObjectCode,
    language: 'c++',
    name: `${name.HybridTSpecSwift}.hpp`,
    subdirectory: [],
    platform: 'ios',
  })
  files.push({
    content: cppHybridObjectCodeCpp,
    language: 'c++',
    name: `${name.HybridTSpecSwift}.cpp`,
    subdirectory: [],
    platform: 'ios',
  })
  return files
}

function getPropertyForwardImplementation(property: Property): string {
  const bridgedType = new SwiftCxxBridgedType(property.type)
  const convertToCpp = bridgedType.parseFromSwiftToCpp(
    `self.__implementation.${property.name}`,
    'swift'
  )
  const convertFromCpp = bridgedType.parseFromCppToSwift('newValue', 'swift')
  const getter = `
@inline(__always)
get {
  return ${indent(convertToCpp, '  ')}
}
  `.trim()
  const setter = `
@inline(__always)
set {
  self.__implementation.${property.name} = ${indent(convertFromCpp, '  ')}
}
  `.trim()

  const body = [getter]
  if (!property.isReadonly) {
    body.push(setter)
  }

  const code = `
public final var ${property.name}: ${bridgedType.getTypeCode('swift')} {
  ${indent(body.join('\n'), '  ')}
}
  `
  return code.trim()
}

function getMethodForwardImplementation(method: Method): string {
  // wrapped return in a std::expected
  const resultType = new ResultWrappingType(method.returnType)
  addKnownType(`expected_${resultType.getCode('c++')}`, resultType, 'swift')
  const bridgedResultType = new SwiftCxxBridgedType(resultType, true)
  const resultBridge = bridgedResultType.getRequiredBridge()
  if (resultBridge == null)
    throw new Error(
      `Result type (${bridgedResultType.getTypeCode('c++')}) does not have a bridge!`
    )
  const bridgedErrorType = new SwiftCxxBridgedType(resultType.error, true)

  const returnType = new SwiftCxxBridgedType(method.returnType, true)
  const params = method.parameters.map((p) => {
    const bridgedType = new SwiftCxxBridgedType(p.type)
    return `${p.name}: ${bridgedType.getTypeCode('swift')}`
  })
  const passParams = method.parameters.map((p) => {
    const bridgedType = new SwiftCxxBridgedType(p.type)
    return `${p.name}: ${bridgedType.parseFromCppToSwift(p.name, 'swift')}`
  })
  let body: string
  if (returnType.hasType) {
    body = `
let __result = try self.__implementation.${method.name}(${passParams.join(', ')})
let __resultCpp = ${returnType.parseFromSwiftToCpp('__result', 'swift')}
return bridge.${resultBridge.funcName}(__resultCpp)
`.trim()
  } else {
    body = `
try self.__implementation.${method.name}(${passParams.join(', ')})
return bridge.${resultBridge.funcName}()
`.trim()
  }

  return `
@inline(__always)
public final func ${method.name}(${params.join(', ')}) -> ${bridgedResultType.getTypeCode('swift')} {
  do {
    ${indent(body, '    ')}
  } catch (let __error) {
    let __exceptionPtr = ${indent(bridgedErrorType.parseFromSwiftToCpp('__error', 'swift'), '    ')}
    return bridge.${resultBridge.funcName}(__exceptionPtr)
  }
}
  `.trim()
}
