#!/usr/bin/pwsh
<#
.SYNOPSIS
Script to crawl AniList for character names
.PARAMETER startPage
Start page number.
Default is 0.
.PARAMETER output
Output filename.
Default is names_anilist.txt.
#>

param(
    [int]$startPage = 0,
    [string]$output = 'names_anilist.txt'
)

# disable extra noise for invoke-webrequest
#$ProgressPreference = "SilentlyContinue"

$requestTemplate = '{ "query": "query { Page (page: {0}, perPage: 50) { characters { name { full } } pageInfo { hasNextPage lastPage } } }" }'
$page = $startPage
$total = 3171
$result = @()
$hasNextPage = $false
$delay = 3

function Request-Page($num)
{
    $json = $requestTemplate.Replace('{0}', $num)
    $response = Invoke-RestMethod 'https://graphql.anilist.co' -Method Post -Body $json -ContentType "application/json" -TimeoutSec 30
    Start-Sleep -Seconds $delay
    return $response.data.Page
}

function Get-TotalPageCount($start)
{
    $min = $start
    $max = $start + 3000
    do
    {
        $response = Request-Page $max
        Write-Progress -Activity "Downloading" -Status "Counting pages... [$min - $max]"
        $max += 1000
    } while ($response.pageInfo.hasNextPage)

    while ($min -lt $max)
    {
        $middle = $min + [int](($max - $min) / 2)
        $response = Request-Page $middle
        if ($response.pageInfo.hasNextPage)
        {
            $min = $middle
        }
        elseif ($response.characters.Count -eq 0)
        {
            $max = $middle
        }
        else
        {
            return $middle
        }
        Write-Progress -Activity "Downloading" -Status "Counting pages... [$min - $max]"
    }
    return $middle
}

try
{
    Write-Progress -Activity "Downloading" -Status "Counting pages..." -PercentComplete 0
    $total = Get-TotalPageCount $startPage
}
catch
{
    Write-Warning "Failed to count total number of pages, using default [$total]"
}

$startTime = [DateTime]::UtcNow
$remainingSeconds = ($total - $page) * 1.7
do
{
    try
    {
        Write-Progress -Activity "Downloading" -Status "Page $page of $total" -PercentComplete ($page * 100.0 / $total) -SecondsRemaining $remainingSeconds
        $response = Request-Page $page
        $hasNextPage = $response.pageInfo.hasNextPage
        $chars = $response.characters
        foreach ($char in $chars)
        {
            $name = $char.name.full
            if ($null -eq $name) { continue }
            
            $name = $char.name.full.Replace('  ', ' ').Replace('`', "'").Trim()
            if (($name.Length -lt 2) -or ("$name" -match '^\d+$'))
            {
                Write-Host "Skipping $name"
                continue 
            }
            $result += $name
        }

        $page++
        $total = [Math]::Max($total, $response.pageInfo.lastPage)
        if (($page - $startPage) -gt 60)
        {
            $remainingSeconds = ([DateTime]::UtcNow - $startTime).TotalSeconds / ($page - $startPage) * ($total - $page)
        }
        else
        {
            $remainingSeconds = ($total - $page) * 1.7
        }
    }
    catch
    {
        Write-Error "Failed to request page $page`: $_"
        $hasNextPage = $false
    }    
} while ($hasNextPage)
Write-Host "Stopped on page $page/$total"
Write-Progress -Activity "Downloading" -Completed

Write-Host 'Saving the results...'
'# https://anilist.co/search/characters' | Out-File -LiteralPath $output
$result | Sort-Object | Get-Unique | Out-File -LiteralPath $output -Append

Write-Host 'Done.'