#ifndef AppVersion
  #define AppVersion "0.2.0"
#endif
#ifndef SourceRoot
  #error SourceRoot must point at the self-contained Companion publish directory.
#endif
#ifndef OutputRoot
  #error OutputRoot must point at the installer output directory.
#endif

[Setup]
AppId={{EDEB45E4-41CB-4D7A-A479-4073A1A08B93}
AppName=Lumi Companion
AppVersion={#AppVersion}
AppVerName=Lumi Companion {#AppVersion}
AppPublisher=OokamiKunTV
AppCopyright=Copyright (c) 2026 OokamiKunTV
DefaultDirName={localappdata}\Programs\Lumi Companion
DefaultGroupName=Lumi Companion
DisableProgramGroupPage=yes
PrivilegesRequired=lowest
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
OutputDir={#OutputRoot}
OutputBaseFilename=Lumi.Companion-Setup
Compression=lzma2/ultra64
SolidCompression=yes
WizardStyle=modern
SetupLogging=yes
CloseApplications=yes
CloseApplicationsFilter=Lumi.Companion.App.exe
RestartApplications=no
UninstallDisplayName=Lumi Companion
UninstallDisplayIcon={app}\Lumi.Companion.App.exe
VersionInfoCompany=OokamiKunTV
VersionInfoCopyright=Copyright (c) 2026 OokamiKunTV
VersionInfoDescription=Lumi Companion installer
VersionInfoProductName=Lumi Companion
VersionInfoProductTextVersion={#AppVersion}
LicenseFile={#SourceRoot}\legal\LUMI-COMPANION-LICENCE.txt
InfoBeforeFile={#SourceRoot}\legal\PRIVACY-NOTICE.txt
InfoAfterFile={#SourceRoot}\legal\THIRD-PARTY-NOTICES.txt

[Files]
Source: "{#SourceRoot}\Lumi.Companion.App.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#SourceRoot}\components\*"; DestDir: "{app}\components"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "{#SourceRoot}\legal\*"; DestDir: "{app}\legal"; Flags: ignoreversion recursesubdirs createallsubdirs

[Icons]
Name: "{autoprograms}\Lumi Companion"; Filename: "{app}\Lumi.Companion.App.exe"
Name: "{autoprograms}\Lumi Companion - Legal and Privacy"; Filename: "{app}\legal\LEGAL-README.txt"

[Run]
Filename: "{app}\Lumi.Companion.App.exe"; Description: "Open Lumi Companion"; Flags: nowait postinstall skipifsilent

[Code]
var
  PairingPackagePath: String;
  PairingHost: String;
  PairingOperatorName: String;
  PairingOperatorContact: String;
  PairingPrivacyUrl: String;
  HostAcknowledgementPage: TInputOptionWizardPage;

function LocatePairingPackage: String;
var
  FindRec: TFindRec;
begin
  Result := '';
  if FindFirst(ExpandConstant('{src}\*.lumi-pairing.json'), FindRec) then
  begin
    try
      Result := AddBackslash(ExpandConstant('{src}')) + FindRec.Name;
    finally
      FindClose(FindRec);
    end;
  end;
end;

function JsonStringValue(const Json: String; const Key: String): String;
var
  Marker: String;
  Tail: String;
  MarkerPos: Integer;
  ColonPos: Integer;
  EndQuotePos: Integer;
begin
  Result := '';
  Marker := '"' + Key + '"';
  MarkerPos := Pos(Marker, Json);
  if MarkerPos = 0 then
    Exit;

  Tail := Copy(Json, MarkerPos + Length(Marker), Length(Json));
  ColonPos := Pos(':', Tail);
  if ColonPos = 0 then
    Exit;

  Tail := Trim(Copy(Tail, ColonPos + 1, Length(Tail)));
  if (Length(Tail) < 2) or (Tail[1] <> '"') then
    Exit;

  Tail := Copy(Tail, 2, Length(Tail));
  EndQuotePos := Pos('"', Tail);
  if EndQuotePos = 0 then
    Exit;

  Result := Copy(Tail, 1, EndQuotePos - 1);
end;

function LoadPairingJson(const PackagePath: String; var Json: String): Boolean;
var
  Lines: TArrayOfString;
  Index: Integer;
begin
  Result := False;
  Json := '';
  if (PackagePath = '') or not LoadStringsFromFile(PackagePath, Lines) then
    Exit;

  for Index := 0 to GetArrayLength(Lines) - 1 do
  begin
    if Index > 0 then
      Json := Json + #10;
    Json := Json + Lines[Index];
  end;
  Result := True;
end;

function ReadPairingHost(const Json: String): String;
var
  Candidate: String;
  LowerCandidate: String;
begin
  Result := '';
  Candidate := JsonStringValue(Json, 'host');
  LowerCandidate := Lowercase(Candidate);
  if ((Pos('https://', LowerCandidate) = 1) or (Pos('http://', LowerCandidate) = 1)) and
     (Length(Candidate) <= 500) then
    Result := Candidate;
end;

procedure InitializeWizard;
var
  HostDisplay: String;
  Explanation: String;
  OperatorDetails: String;
  Json: String;
begin
  PairingPackagePath := LocatePairingPackage;
  if LoadPairingJson(PairingPackagePath, Json) then
  begin
    PairingHost := ReadPairingHost(Json);
    PairingOperatorName := Copy(JsonStringValue(Json, 'operator_name'), 1, 200);
    PairingOperatorContact := Copy(JsonStringValue(Json, 'operator_contact'), 1, 300);
    PairingPrivacyUrl := Copy(JsonStringValue(Json, 'privacy_url'), 1, 500);
  end;

  if PairingHost <> '' then
    HostDisplay := PairingHost
  else
    HostDisplay := 'No paired Lumi host was identified beside this installer.';

  OperatorDetails := '';
  if PairingOperatorName <> '' then
    OperatorDetails := OperatorDetails + #13#10 + 'Operator name: ' + PairingOperatorName;
  if PairingOperatorContact <> '' then
    OperatorDetails := OperatorDetails + #13#10 + 'Operator contact: ' + PairingOperatorContact;
  if PairingPrivacyUrl <> '' then
    OperatorDetails := OperatorDetails + #13#10 + 'Privacy information: ' + PairingPrivacyUrl;

  Explanation :=
    'This copy of Lumi Companion is intended to connect to:' + #13#10#13#10 +
    HostDisplay + OperatorDetails + #13#10#13#10 +
    'The person or organisation controlling that Lumi installation is the Host Operator. ' +
    'The Host Operator is responsible for the Hosted Service it controls, including server configuration, ' +
    'access, server-side data processing, retention, integrations, and required user notices. ' +
    'OokamiKunTV is the software developer and is not automatically the operator of an independently hosted Lumi installation. ' +
    'Responsibility always follows the actual facts and applicable law.';

  HostAcknowledgementPage := CreateInputOptionPage(
    wpInfoBefore,
    'Lumi host and data responsibility',
    'Review the host that will receive Companion data.',
    Explanation,
    False,
    False);
  HostAcknowledgementPage.Add(
    'I understand which Lumi host this Companion will use and that the Host Operator controls its server-side service and processing.');
end;

function NextButtonClick(CurPageID: Integer): Boolean;
begin
  Result := True;
  { A silent deployment is an administrator/operator action and has no wizard
    UI in which an interactive acknowledgement can be collected. }
  if WizardSilent then
    Exit;

  if (HostAcknowledgementPage <> nil) and (CurPageID = HostAcknowledgementPage.ID) and
     not HostAcknowledgementPage.Values[0] then
  begin
    MsgBox(
      'Confirm that you understand the host and operator responsibility before continuing.',
      mbInformation,
      MB_OK);
    Result := False;
  end;
end;

procedure ImportPairingPackage;
var
  SourcePath: String;
  TargetPath: String;
begin
  { Portable and installed builds share the same DPAPI credential directory. Do
    not create another authorized device merely because an existing user moves
    the executable into its durable installed location. }
  if FileExists(ExpandConstant('{localappdata}\Lumi\Companion\device.credential')) then
    Exit;

  SourcePath := PairingPackagePath;
  if SourcePath = '' then
    SourcePath := LocatePairingPackage;
  if SourcePath = '' then
    Exit;

  TargetPath := AddBackslash(ExpandConstant('{app}')) + ExtractFileName(SourcePath);
  if not CopyFile(SourcePath, TargetPath, False) then
    MsgBox(
      'Lumi Companion was installed, but its one-time pairing package could not be imported. ' +
      'Open Companion and choose the pairing file manually.',
      mbError,
      MB_OK);
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
  if CurStep = ssPostInstall then
    ImportPairingPackage;
end;
