---
- name: Enable required Windows features for WSL
  tags: [packages, services, wsl]
  ansible.windows.win_optional_feature:
    name:
      - Microsoft-Windows-Subsystem-Linux
      - VirtualMachinePlatform
    state: present
    include_parent: true

- name: Gather Windows host version details
  tags: [packages]
  ansible.windows.win_powershell:
    script: |
      $os = Get-ComputerInfo
      $buildNumber = [int]$os.OsBuildNumber
      $Ansible.Result = @{
          product_name = $os.WindowsProductName
          build_number = $buildNumber
          is_windows_11 = $buildNumber -ge 22000
      }
      $Ansible.Changed = $false
  register: windows_host_version_state

- name: Fail when Windows host is not Windows 11
  tags: [packages]
  ansible.builtin.fail:
    msg: >-
      workstation_host_windows is supported only on Windows 11. Detected {{ windows_host_version_state.result.product_name }}
      build {{ windows_host_version_state.result.build_number }}.
  when: not (windows_host_version_state.result.is_windows_11 | default(false))

- name: Enable dark mode for Windows apps
  tags: [packages]
  ansible.windows.win_regedit:
    path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize
    name: AppsUseLightTheme
    data: 0
    type: dword
  register: windows_apps_dark_mode_state
  when: windows_enable_dark_theme | default(false)

- name: Enable dark mode for Windows system surfaces
  tags: [packages]
  ansible.windows.win_regedit:
    path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize
    name: SystemUsesLightTheme
    data: 0
    type: dword
  register: windows_system_dark_mode_state
  when: windows_enable_dark_theme | default(false)

- name: Hide Windows taskbar search box
  tags: [packages]
  ansible.windows.win_regedit:
    path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Search
    name: SearchboxTaskbarMode
    data: 0
    type: dword
  register: windows_taskbar_search_state
  when: windows_hide_taskbar_search | default(false)

- name: Hide Windows 11 taskbar widgets
  tags: [packages]
  ansible.windows.win_regedit:
    path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
    name: TaskbarDa
    data: 0
    type: dword
  register: windows_11_widgets_state
  when: windows_hide_taskbar_widgets | default(false)

- name: Disable Windows 11 widgets via policy
  tags: [packages]
  ansible.windows.win_regedit:
    path: HKLM:\SOFTWARE\Policies\Microsoft\Dsh
    name: AllowNewsAndInterests
    data: 0
    type: dword
  register: windows_11_widgets_policy_state
  when: windows_hide_taskbar_widgets | default(false)

- name: Note when Windows shell settings may require sign out
  tags: [packages]
  ansible.builtin.debug:
    msg: >-
      Windows shell appearance settings changed. Sign out and back in, or restart explorer.exe,
      if taskbar or theme updates do not appear immediately. Windows 11 widget policy changes can require
      signing out before the widget button disappears from the taskbar.
  changed_when: false
  when: >-
    (windows_apps_dark_mode_state is changed)
    or (windows_system_dark_mode_state is changed)
    or (windows_taskbar_search_state is changed)
    or (windows_11_widgets_state is changed)
    or (windows_11_widgets_policy_state is changed)

- name: Ensure winget is executable on Windows host through PSRP
  tags: [packages]
  when: windows_package_backend == 'winget_psrp'
  ansible.windows.win_powershell:
    script: |
      $winget = Get-Command winget.exe -ErrorAction SilentlyContinue
      if ($null -eq $winget) {
          throw 'winget.exe is not available on the Windows host. Install App Installer or rerun the bootstrap script.'
      }

      & $winget.Source --info *> $null
      if ($LASTEXITCODE -ne 0) {
          throw 'winget.exe is not executable through the current PSRP session. Set windows_package_backend to winget_wsl_local on WSL-managed hosts.'
      }

      $Ansible.Changed = $false

- name: Ensure winget is executable through local Windows PowerShell from WSL
  tags: [packages]
  when: windows_package_backend == 'winget_wsl_local'
  block:
    - name: Ensure winget_wsl_local backend is running inside WSL
      ansible.builtin.assert:
        that:
          - lookup('ansible.builtin.env', 'WSL_DISTRO_NAME') | length > 0
        fail_msg: >-
          winget_wsl_local requires running the playbook from inside the
          target machine's WSL environment.

    - name: Get local Windows host name through WSL interop
      ansible.builtin.shell: |
        powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command '$env:COMPUTERNAME'
      args:
        executable: /bin/bash
      delegate_to: localhost
      register: local_windows_host_name_state
      changed_when: false

    - name: Get local Windows host IP addresses through WSL interop
      ansible.builtin.shell: |
        powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command '
          Get-NetIPAddress -AddressFamily IPv4, IPv6 -ErrorAction SilentlyContinue |
            Where-Object {
              $_.IPAddress -notin @("127.0.0.1", "::1") -and
              $_.IPAddress -notlike "169.254.*" -and
              $_.IPAddress -notlike "fe80:*"
            } |
            Select-Object -ExpandProperty IPAddress
        '
      args:
        executable: /bin/bash
      delegate_to: localhost
      register: local_windows_host_ip_state
      changed_when: false

    - name: Ensure winget_wsl_local backend targets the local Windows host
      ansible.builtin.assert:
        that:
          - >-
            (ansible_host | lower) in ['localhost', '127.0.0.1', '::1']
            or (local_windows_host_name_state.stdout | trim | upper)
            == ((ansible_host | regex_replace('\\..*$', '')) | upper)
            or (ansible_host | lower)
            in (local_windows_host_ip_state.stdout_lines | map('trim') | map('lower') | list)
        fail_msg: >-
          winget_wsl_local can only target the local Windows host reached
          through WSL interop. Local Windows host '{{ local_windows_host_name_state.stdout | trim }}'
          with addresses {{ local_windows_host_ip_state.stdout_lines | map('trim') | list }}
          does not match ansible_host '{{ ansible_host }}'.

    - name: Ensure winget is executable through local Windows PowerShell
      ansible.builtin.shell: |
        powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command '
          $ErrorActionPreference = "Stop"
          $winget = Get-Command winget.exe -ErrorAction SilentlyContinue
          if ($null -eq $winget) {
              throw "winget.exe is not available on the local Windows host. Install App Installer or rerun the bootstrap script."
          }

          & $winget.Source --info *> $null
          if ($LASTEXITCODE -ne 0) {
              throw "winget.exe is not executable through local Windows PowerShell interop."
          }
        '
      args:
        executable: /bin/bash
      delegate_to: localhost
      changed_when: false

- name: Ensure WSL 2 is the default backend
  tags: [packages, services, wsl]
  ansible.windows.win_powershell:
    script: |
      $status = & wsl --status 2>$null
      if ($LASTEXITCODE -eq 0 -and $status -match 'Default Version:\s*2') {
          $Ansible.Changed = $false
          return
      }

      & wsl --set-default-version 2
      if ($LASTEXITCODE -ne 0) {
          throw 'Failed to set WSL default version to 2.'
      }

      $Ansible.Changed = $true

- name: Install Windows workstation applications with winget through PSRP
  tags: [packages]
  when: windows_package_backend == 'winget_psrp'
  ansible.windows.win_powershell:
    script: |
      $packageId = '{{ item.id }}'
      $packageName = '{{ item.name | default(item.id) }}'
      $packageSource = '{{ item.source | default('') }}'
      $listArgs = @(
          'list'
          '--id', $packageId
          '--exact'
          '--accept-source-agreements'
          '--disable-interactivity'
      )
      $installArgs = @(
          'install'
          '--id', $packageId
          '--exact'
          '--silent'
          '--accept-package-agreements'
          '--accept-source-agreements'
          '--disable-interactivity'
      )

      if (-not [string]::IsNullOrWhiteSpace($packageSource)) {
          $listArgs += @('--source', $packageSource)
          $installArgs += @('--source', $packageSource)
      }

      $installed = & winget @listArgs 2>$null
      if ($LASTEXITCODE -eq 0 -and $installed -match [regex]::Escape($packageId)) {
          $Ansible.Changed = $false
          return
      }

      & winget @installArgs
      if ($LASTEXITCODE -ne 0) {
          throw "Failed to install $packageName with winget"
      }

      $Ansible.Changed = $true
  loop: "{{ windows_winget_packages | default([]) }}"
  loop_control:
    label: "{{ item.id }}"

- name: Install Windows workstation applications with winget through WSL local backend
  tags: [packages]
  when: windows_package_backend == 'winget_wsl_local'
  ansible.builtin.shell: |
    powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command '
      $ErrorActionPreference = "Stop"
      $packageId = "{{ item.id }}"
      $packageName = "{{ item.name | default(item.id) }}"
      $packageSource = "{{ item.source | default('') }}"
      $winget = Get-Command winget.exe -ErrorAction Stop
      $listArgs = @(
          "list"
          "--id", $packageId
          "--exact"
          "--accept-source-agreements"
          "--disable-interactivity"
      )
      $installArgs = @(
          "install"
          "--id", $packageId
          "--exact"
          "--silent"
          "--accept-package-agreements"
          "--accept-source-agreements"
          "--disable-interactivity"
      )

      if (-not [string]::IsNullOrWhiteSpace($packageSource)) {
          $listArgs += @("--source", $packageSource)
          $installArgs += @("--source", $packageSource)
      }

      $installed = & $winget.Source @listArgs 2>$null
      if ($LASTEXITCODE -eq 0 -and $installed -match [regex]::Escape($packageId)) {
          [Console]::Out.WriteLine("changed=false")
          exit 0
      }

      & $winget.Source @installArgs
      if ($LASTEXITCODE -ne 0) {
          throw "Failed to install $packageName with winget"
      }

      [Console]::Out.WriteLine("changed=true")
    '
  args:
    executable: /bin/bash
  delegate_to: localhost
  register: windows_wsl_local_winget_install_state
  changed_when: "'changed=true' in windows_wsl_local_winget_install_state.stdout"
  loop: "{{ windows_winget_packages | default([]) }}"
  loop_control:
    label: "{{ item.id }}"

- name: Configure Windows taskbar pin layout
  tags: [packages]
  when: (windows_taskbar_pins | default([])) | length > 0
  block:
    - name: Ensure Windows taskbar layout directory exists
      ansible.windows.win_file:
        path: "{{ windows_taskbar_layout_directory }}"
        state: directory

    - name: Render Windows taskbar layout policy file
      ansible.windows.win_template:
        src: taskbar-layout.xml.j2
        dest: "{{ windows_taskbar_layout_path }}"
      register: windows_taskbar_layout_file_state

    - name: Enable Windows taskbar layout policy
      ansible.windows.win_regedit:
        path: HKCU:\Software\Policies\Microsoft\Windows\Explorer
        name: LockedStartLayout
        data: 1
        type: dword
      register: windows_taskbar_layout_policy_state

    - name: Set Windows taskbar layout policy file path
      ansible.windows.win_regedit:
        path: HKCU:\Software\Policies\Microsoft\Windows\Explorer
        name: StartLayoutFile
        data: "{{ windows_taskbar_layout_path }}"
        type: expandstring
      register: windows_taskbar_layout_policy_path_state

    - name: Set Windows taskbar layout policy refresh behavior
      ansible.windows.win_regedit:
        path: HKCU:\Software\Policies\Microsoft\Windows\Explorer
        name: ReapplyStartLayoutEveryLogon
        data: "{{ (windows_taskbar_reapply_every_logon | default(false)) | ternary(1, 0) }}"
        type: dword
      register: windows_taskbar_layout_policy_reapply_state

    - name: Note when Windows taskbar pin layout may require sign out
      ansible.builtin.debug:
        msg: >-
          Windows taskbar pin policy changed. Sign out and back in if the updated pin order does not appear immediately.
      changed_when: false
      when: >-
        (windows_taskbar_layout_file_state is changed)
        or (windows_taskbar_layout_policy_state is changed)
        or (windows_taskbar_layout_policy_path_state is changed)
        or (windows_taskbar_layout_policy_reapply_state is changed)

- name: Install VS Code WSL extensions on Windows host
  tags: [packages, vscode]
  ansible.windows.win_powershell:
    script: |
      $extensionId = '{{ item }}'
      $code = Get-Command code.cmd -ErrorAction SilentlyContinue
      if ($null -eq $code) {
          throw 'code.cmd is not available. Ensure Visual Studio Code is installed before managing extensions.'
      }

      $installedExtensions = & $code.Source --list-extensions
      if ($installedExtensions -contains $extensionId) {
          $Ansible.Changed = $false
          return
      }

      & $code.Source --install-extension $extensionId --force
      if ($LASTEXITCODE -ne 0) {
          throw "Failed to install VS Code extension $extensionId"
      }

      $Ansible.Changed = $true
  loop: "{{ windows_vscode_extensions | default([]) }}"
  loop_control:
    label: "{{ item }}"

- name: Set Windows Terminal default profile to Ubuntu
  tags: [packages, wsl]
  ansible.windows.win_powershell:
    script: |
      $terminalSettingsPath = Join-Path $env:LOCALAPPDATA 'Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json'
      if (-not (Test-Path $terminalSettingsPath)) {
          throw 'Windows Terminal settings.json was not found. Launch Windows Terminal once before applying the default profile configuration.'
      }

      $settings = Get-Content -Path $terminalSettingsPath -Raw | ConvertFrom-Json
      if ($null -eq $settings.profiles -or $null -eq $settings.profiles.list) {
          throw 'Windows Terminal settings.json does not contain a profiles.list section.'
      }

      $targetProfile = $settings.profiles.list |
          Where-Object {
              $_.name -eq '{{ windows_terminal_default_profile_name }}' -and -not ($_.PSObject.Properties.Name -contains 'hidden' -and $_.hidden)
          } |
          Select-Object -First 1

      if ($null -eq $targetProfile) {
          throw "Windows Terminal visible profile '{{ windows_terminal_default_profile_name }}' was not found. Ensure the Ubuntu WSL profile exists and is not hidden in Windows Terminal first."
      }

      if ($settings.defaultProfile -eq $targetProfile.guid) {
          $Ansible.Changed = $false
          return
      }

      $settings.defaultProfile = $targetProfile.guid
      $settings | ConvertTo-Json -Depth 100 | Set-Content -Path $terminalSettingsPath -Encoding utf8
      $Ansible.Changed = $true
