#!/usr/bin/env bash
# Panewise free Duo check: what will break on iPhone Duo. Read-only; changes nothing.
# Usage: curl -fsSL https://panewise.dev/check.sh | bash          (from your project root)
#        bash check.sh [project-root]
# Free to use. The full Panewise skill (the fixes) is at https://panewise.dev
# A line you have reviewed and want to keep can be silenced with a `panewise:ok` comment on it.
set -uo pipefail

ROOT="${1:-.}"
cd "$ROOT" || { echo "No such directory: $ROOT"; exit 1; }

EXCLUDES=(--exclude-dir=node_modules --exclude-dir=Pods --exclude-dir=build --exclude-dir=.build
  --exclude-dir=DerivedData --exclude-dir=.git --exclude-dir=.dart_tool --exclude-dir=.expo
  --exclude-dir=dist --exclude-dir=android --exclude-dir=.claude --exclude-dir=web --exclude-dir=__tests__
  --exclude-dir='*Watch*' --exclude-dir='*Widget*' --exclude-dir='*Extension*' --exclude-dir=macos
  --exclude='*.test.*' --exclude='*.spec.*')
MAX=12
blockers=0
warnings=0

h()    { printf '\n## %s\n\n' "$1"; }
ok()   { printf -- '- ✅ %s\n' "$1"; }
warn() { printf -- '- ⚠️  %s\n' "$1"; warnings=$((warnings + 1)); }
bad()  { printf -- '- ⛔ %s\n' "$1"; blockers=$((blockers + 1)); }

# hits "label" "regex" include-glob...  → prints up to MAX file:line matches
hits() {
  local label="$1" re="$2"; shift 2
  local inc=() g
  for g in "$@"; do inc+=(--include="$g"); done
  local out n
  out=$(grep -rnE "${EXCLUDES[@]}" "${inc[@]}" "$re" . 2>/dev/null)
  out=$(printf '%s\n' "$out" | grep -v 'panewise:ok')   # a reviewed line, acknowledged in a comment
  if [ -n "${HITS_NOT:-}" ]; then out=$(printf '%s\n' "$out" | grep -vE "$HITS_NOT"); fi
  [ -z "$out" ] && return 1
  n=$(printf '%s\n' "$out" | wc -l | tr -d ' ')
  if [ -n "${HITS_INFO:-}" ]; then ok "$label ($n)"; else warn "$label ($n)"; fi
  printf '%s\n' "$out" | head -n "$MAX" | cut -c1-160 | sed 's/^/    /'
  [ "$n" -gt "$MAX" ] && printf '    … and %s more\n' $((n - MAX))
  return 0
}

echo "# iPhone Duo check: $(basename "$(pwd)")  (free, by panewise.dev)"
echo
echo "_Read-only. Blockers stop the app working natively on iPhone Duo; warnings are layout work._"

# ── Stack ────────────────────────────────────────────────────────────────────
h "Stack"
STACK=()
if [ -f package.json ] && grep -q '"expo"' package.json; then
  ver=$(grep -oE '"expo"[[:space:]]*:[[:space:]]*"[^"]+"' package.json | grep -oE '[0-9]+' | head -1)
  STACK+=(expo)
  echo "- Expo SDK ${ver:-?}"
  if [ -n "${ver:-}" ] && [ "$ver" -lt 57 ] && [ -d ios ] && grep -rqs UIApplicationSceneManifest ios --include=Info.plist; then
    ok "Expo SDK $ver with a committed ios/ that already has the scene manifest (the hand-added route, proven on SDK 54)."
  elif [ -n "${ver:-}" ] && [ "$ver" -lt 57 ] && [ -d ios ]; then
    warn "Expo SDK $ver with a committed ios/: add the scene manifest + SceneDelegate by hand (proven on SDK 54), and expect native deps to need upgrades for Xcode 27.1 (panewise.dev/iphone-duo#scenes)."
  elif [ -n "${ver:-}" ] && [ "$ver" -lt 57 ]; then
    bad "Expo SDK $ver: scene support arrives in SDK 57 (ios.enableSceneSupport) and Xcode 27.1 pairs with SDK 58. Managed project: upgrade the SDK or add a config plugin first (panewise.dev/iphone-duo#scenes)."
  fi
fi
if [ -f package.json ] && grep -q '"react-native"' package.json; then STACK+=(rn); echo "- React Native $(grep -oE '"react-native"[[:space:]]*:[[:space:]]*"[^"]+"' package.json | sed 's/.*: *//')"; fi
if [ -f pubspec.yaml ]; then STACK+=(flutter); echo "- Flutter ($(grep -m1 -E '^\s*sdk:' pubspec.yaml | sed 's/^ *//'))"; fi
if grep -rqlE "${EXCLUDES[@]}" --include='*.swift' '@main' . 2>/dev/null; then
  if grep -rqE "${EXCLUDES[@]}" --include='*.swift' ':[[:space:]]*App[[:space:]]*\{' . 2>/dev/null; then STACK+=(swiftui); echo "- SwiftUI App life cycle (scene-based at runtime; the built Info.plist still needs the manifest, SwiftUI variant at panewise.dev/iphone-duo#scenes)"; fi
fi
if find . -name '*.xcodeproj' -not -path '*/node_modules/*' -not -path '*/Pods/*' -not -path '*/.claude/*' 2>/dev/null | grep -q .; then STACK+=(xcode); echo "- Xcode project: $(find . -name '*.xcodeproj' -not -path '*/node_modules/*' -not -path '*/Pods/*' -not -path '*/.claude/*' | head -3 | tr '\n' ' ')"; fi
GEN=""
if [ -f project.yml ] || [ -f project.yaml ]; then GEN="XcodeGen (project.yml)"; elif [ -f Project.swift ]; then GEN="Tuist (Project.swift)"; fi
[ -n "$GEN" ] && echo "- Generated project: $GEN. Info.plist is rewritten on every generate, so put Info.plist keys in the generator's config (panewise.dev/iphone-duo#scenes)."
[ ${#STACK[@]} -eq 0 ] && [ -z "$GEN" ] && echo "- Couldn't detect a stack. Run this from the project root."

# ── Blockers: scenes ─────────────────────────────────────────────────────────
h "Scene life cycle"
app_plists=$(grep -rlE "${EXCLUDES[@]}" --include='Info.plist' 'LSRequiresIPhoneOS|UILaunchStoryboardName|UILaunchScreen' . 2>/dev/null)
if [ -z "$app_plists" ]; then
  if printf '%s\n' "${STACK[@]:-}" | grep -qx expo; then
    echo "- No committed ios/ folder (managed / CNG). Scene support comes from the Expo SDK, see above."
  else
    echo "- No app Info.plist found."
  fi
else
  while IFS= read -r p; do
    if grep -q 'UIApplicationSceneManifest' "$p"; then ok "$p has UIApplicationSceneManifest"
    elif [ -n "$GEN" ] && grep -qs 'UIApplicationSceneManifest' project.yml project.yaml Project.swift; then
      warn "$p has no UIApplicationSceneManifest yet, but the $GEN config does. Regenerate the project."
    elif printf '%s\n' "${STACK[@]:-}" | grep -qx swiftui; then bad "$p has no UIApplicationSceneManifest. Add the SwiftUI variant, with no scene delegate class (panewise.dev/iphone-duo#scenes)."
    else bad "$p has no UIApplicationSceneManifest. The Duo boxes the app and may drop its touches; iOS 27 SDK builds won't launch (panewise.dev/iphone-duo#scenes)."; fi
  done <<< "$app_plists"
fi

# ── Blockers: toolchain ──────────────────────────────────────────────────────
h "Toolchain"
if command -v xcodebuild >/dev/null 2>&1; then
  xv=$(xcodebuild -version 2>/dev/null | head -1)
  if [ -n "${DEVELOPER_DIR:-}" ]; then echo "- Using: ${xv:-unknown} (DEVELOPER_DIR=$DEVELOPER_DIR)"
  else echo "- Active: ${xv:-unknown} ($(xcode-select -p 2>/dev/null))"; fi
  major=$(printf '%s' "$xv" | grep -oE '[0-9]+' | head -1)
  minor=$(printf '%s' "$xv" | grep -oE '[0-9]+\.[0-9]+' | head -1 | cut -d. -f2)
  if [ -n "${major:-}" ] && { [ "$major" -lt 27 ] || { [ "$major" -eq 27 ] && [ "${minor:-0}" -lt 1 ]; }; }; then
    x27=$(ls -d /Applications/Xcode*27.1*.app 2>/dev/null | head -1)
    if [ -n "$x27" ]; then
      warn "Builds from this Xcode run on the Duo in compatibility mode (letterboxed). Build with DEVELOPER_DIR=$x27/Contents/Developer (and ship store builds from Xcode 27.1+)."
    else
      warn "Builds from this Xcode run on the Duo in compatibility mode (letterboxed). Install Xcode 27.1+ beside it and build with DEVELOPER_DIR."
    fi
  fi
  ls -d /Applications/Xcode*27.1*.app >/dev/null 2>&1 && ok "Xcode 27.1 is installed: $(ls -d /Applications/Xcode*27.1*.app | head -1)"
fi
if [ -f eas.json ]; then
  img=$(grep -oE '"image"[[:space:]]*:[[:space:]]*"[^"]+"' eas.json | head -3 | tr '\n' ' ')
  if [ -n "$img" ]; then echo "- eas.json images: $img"; else warn "eas.json pins no iOS image. Make sure your store builds use Xcode 27.1+."; fi
fi

# ── Fit: side insets, widths, modals ─────────────────────────────────────────
h "Fit (side status strip, widths, modals)"
found=0
hits "Window width read directly; may be one status-strip too wide, or stale after a fold" \
  "Dimensions\.get\(['\"]window['\"]\)|UIScreen\.main\.bounds|MediaQuery\.of\(context\)\.size\.width" \
  '*.ts' '*.tsx' '*.js' '*.jsx' '*.swift' '*.m' '*.dart' && found=1
hits "Hard-coded phone widths" \
  "(width|maxWidth|minWidth)[[:space:]]*[:=][[:space:]]*(320|375|390|393|402|414|428|430|440)\b" \
  '*.ts' '*.tsx' '*.js' '*.jsx' '*.swift' '*.dart' && found=1
HITS_NOT="'left'|\"left\"|'right'|\"right\"" hits "Safe-area edges set without left/right (check each: the Duo's status strip is a side inset)" \
  "edges=\{\[[^]]*\]\}" '*.tsx' '*.jsx' && found=1
HITS_NOT="ignoresSafeArea\((\.[a-z]+, *)?edges: *\.(top|bottom)\)|ignoresSafeArea\((\.[a-z]+, *)?edges: *\[\.(top|bottom)(, *\.(top|bottom))?\]\)" hits "Safe area ignored on the sides (top/bottom only is fine)" \
  "ignoresSafeArea|edgesIgnoringSafeArea|(left|right):[[:space:]]*false" '*.swift' '*.dart' && found=1
hits "Modals: often a new safe-area context, check each one unfolded" \
  "<Modal[[:space:]>]|fullScreenCover|\.sheet\(|showModalBottomSheet|present\(.*animated" \
  '*.tsx' '*.jsx' '*.swift' '*.dart' && found=1
[ $found -eq 0 ] && ok "Nothing obvious."

# ── Unfold: iPad gates, tabs ─────────────────────────────────────────────────
h "Unfold (two panes)"
found=0
hits "iPad-only gates: the Duo is a phone idiom and never passes these (mark reviewed ones panewise:ok)" \
  "Platform\.isPad|isPad\b|userInterfaceIdiom[[:space:]]*==[[:space:]]*\.pad|\.pad\)" \
  '*.ts' '*.tsx' '*.js' '*.jsx' '*.swift' '*.m' && found=1
hits "Tab bars: native ones become a right-edge rail unfolded; decide rail vs. sidebar (panewise.dev/iphone-duo#tabs)" \
  "createBottomTabNavigator|createNativeBottomTabNavigator|(class[[:space:]]+[A-Za-z0-9_]+[[:space:]]*:[[:space:]]*UITabBarController([^A-Za-z]|$)|UITabBarController\()|TabView[[:space:]]*\{|TabView\(|BottomNavigationBar|NavigationBar\(" \
  '*.ts' '*.tsx' '*.js' '*.jsx' '*.swift' '*.dart' && found=1
HITS_INFO=1 hits "Existing split views (good starting points)" \
  "NavigationSplitView|UISplitViewController" '*.swift' && found=1
[ $found -eq 0 ] && echo "- No tab bars or iPad gates found. Pick list → detail screens by hand (panewise.dev/iphone-duo#panes)."

# ── Summary ──────────────────────────────────────────────────────────────────
h "Summary"
echo "- Blockers: $blockers"
echo "- Warnings: $warnings"
if [ $blockers -gt 0 ]; then echo; echo "Fix the blockers first (panewise.dev/iphone-duo#scenes). Layout work can't show on the Duo until they're done."; fi
echo
echo "---"
echo "Want all of this fixed for you? Panewise is a Claude Code skill: say \"make my app ready for iPhone Duo\""
echo "and it fixes the blockers and the side status strip, and builds two-pane layouts. https://panewise.dev"
echo "Every issue above, explained: https://panewise.dev/iphone-duo"
